diff --git a/CMakeLists.txt b/CMakeLists.txt index 105e3ac1a04e741e94011702086ba31f1df61ab1..82a5848331bdf4e0a5d841461bdc230c69593693 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,7 @@ ELSEIF(CMAKE_CXX_COMPILER_ID MATCHES "Clang") ENDIF() # -- OPTIONS -OPTION(USE_CODI "Compile with CoDiPack" ON) +OPTION(USE_CODI "Compile with CoDiPack" OFF) # -- DEPENDENCIES # Python diff --git a/sdpm/_src/sdpmw.i b/sdpm/_src/sdpmw.i index b27441a228f732ce52aa2a230dbc9b2b332019ee..289bf69eecf24636b12a54e54daffe7eafe065e2 100644 --- a/sdpm/_src/sdpmw.i +++ b/sdpm/_src/sdpmw.i @@ -43,6 +43,7 @@ threads="1" #include "sdpmSolver.h" #include "sdpmSolverTransonic.h" #include "sdpmAdjoint.h" +#include "sdpmGradient.h" #include "sdpmCppBuf2Py.h" @@ -140,6 +141,7 @@ typedef double sdpmDouble; // needed so that SWIG does not convert sdpmDouble to %include "sdpmSolver.h" %include "sdpmSolverTransonic.h" %include "sdpmAdjoint.h" +%include "sdpmGradient.h" %warnfilter(401); // Nothing known about base class 'std::basic_streambuf< char >' %include "sdpmCppBuf2Py.h" diff --git a/sdpm/src/sdpm.h b/sdpm/src/sdpm.h index 0fa95980af2d93c94b558621de308d04a43c3003..70878202c7b043945413b18d8abc993ce878054e 100644 --- a/sdpm/src/sdpm.h +++ b/sdpm/src/sdpm.h @@ -81,6 +81,7 @@ class Builder; class Solver; class SolverTransonic; class Adjoint; +class Gradient; } // namespace sdpm #endif // SDPM_H diff --git a/sdpm/src/sdpmGradient.cpp b/sdpm/src/sdpmGradient.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cfa0a806155ec5f09cbf626009ffe9eac5cca13f --- /dev/null +++ b/sdpm/src/sdpmGradient.cpp @@ -0,0 +1,310 @@ +/* + * Copyright 2023 University of Liège + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sdpmGradient.h" +#include "sdpmSolver.h" +#include "sdpmProblem.h" +#include "sdpmBody.h" +#include <iostream> + +using namespace sdpm; + +Gradient::Gradient(Solver &sol) : _sol(sol), + _nF(_sol._pbl._omega.size()), + _nM(_sol._pbl.getNumModes()) +{ + // Create node row identification map + int inod = 0; + for (auto body : _sol._pbl.getBodies()) + for (auto n : body->getNodes()) + _rowsN.insert(std::pair<Node *, int>(n, inod++)); + _nN = _rowsN.size(); + + // Resize displacement and load containers + _dz.resize(_nM, sdpmVectorXd::Zero(_nN)); + _clz1Re.resize(_nM, std::vector<sdpmVectorXd>(_nF, sdpmVectorXd::Zero(_nN))); + _clz1Im.resize(_nM, std::vector<sdpmVectorXd>(_nF, sdpmVectorXd::Zero(_nN))); + + // Resize pressure gradient containers + _dCp1Re_a.resize(_nF, sdpmVectorXd::Zero(_sol._nP)); + _dCp1Im_a.resize(_nF, sdpmVectorXd::Zero(_sol._nP)); + _dCp1Re_rx.resize(_nF, sdpmMatrixXd::Zero(_sol._nP, _sol._nP)); + _dCp1Im_rx.resize(_nF, sdpmMatrixXd::Zero(_sol._nP, _sol._nP)); + _dCp1Re_ry.resize(_nF, sdpmMatrixXd::Zero(_sol._nP, _sol._nP)); + _dCp1Im_ry.resize(_nF, sdpmMatrixXd::Zero(_sol._nP, _sol._nP)); + _dCp1Re_aDot.resize(_nF, sdpmVectorXd::Zero(_sol._nP)); + _dCp1Im_aDot.resize(_nF, sdpmVectorXd::Zero(_sol._nP)); + _dCp1Re_hDot.resize(_nF, sdpmVectorXd::Zero(_sol._nP)); + _dCp1Im_hDot.resize(_nF, sdpmVectorXd::Zero(_sol._nP)); + _dCp1Re_rxDot.resize(_nF, sdpmMatrixXd::Zero(_sol._nP, _sol._nP)); + _dCp1Im_rxDot.resize(_nF, sdpmMatrixXd::Zero(_sol._nP, _sol._nP)); + _dCp1Re_ryDot.resize(_nF, sdpmMatrixXd::Zero(_sol._nP, _sol._nP)); + _dCp1Im_ryDot.resize(_nF, sdpmMatrixXd::Zero(_sol._nP, _sol._nP)); + _dCp1Re_dzDot.resize(_nF, sdpmMatrixXd::Zero(_sol._nP, _sol._nP)); + _dCp1Im_dzDot.resize(_nF, sdpmMatrixXd::Zero(_sol._nP, _sol._nP)); + _dCp1Re_aDotDot.resize(_nF, sdpmVectorXd::Zero(_sol._nP)); + _dCp1Im_aDotDot.resize(_nF, sdpmVectorXd::Zero(_sol._nP)); + _dCp1Re_hDotDot.resize(_nF, sdpmVectorXd::Zero(_sol._nP)); + _dCp1Im_hDotDot.resize(_nF, sdpmVectorXd::Zero(_sol._nP)); + _dCp1Re_dzDotDot.resize(_nF, sdpmMatrixXd::Zero(_sol._nP, _sol._nP)); + _dCp1Im_dzDotDot.resize(_nF, sdpmMatrixXd::Zero(_sol._nP, _sol._nP)); + + // Resize load gradient containers + _dClz1Re_rx.resize(_nF, sdpmMatrixXd::Zero(_nN, _nN)); + _dClz1Im_rx.resize(_nF, sdpmMatrixXd::Zero(_nN, _nN)); + _dClz1Re_ry.resize(_nF, sdpmMatrixXd::Zero(_nN, _nN)); + _dClz1Im_ry.resize(_nF, sdpmMatrixXd::Zero(_nN, _nN)); + _dClz1Re_dz.resize(_nF, sdpmMatrixXd::Zero(_nN, _nN)); + _dClz1Im_dz.resize(_nF, sdpmMatrixXd::Zero(_nN, _nN)); +} + +Gradient::~Gradient() +{ + std::cout << "~sdpm::Gradient()\n"; +} + +/** + * @brief Compute partial derivatives of pressure and loads with respect to degrees of freedom + */ +void Gradient::run() +{ + // Store loads and displacements + for (auto body : _sol._pbl.getBodies()) + { + for (size_t imd = 0; imd < _nM; ++imd) + { + std::vector<sdpmDouble> dz = body->getModeZ(imd); + for (auto n : body->getNodes()) + _dz[imd](_rowsN.at(n)) = dz[n->getId() - 1]; + for (size_t ifq = 0; ifq < _nF; ++ifq) + { + std::vector<sdpmVector3d> clzre = body->getUnsteadyLoadsReal(imd, ifq); + std::vector<sdpmVector3d> clzim = body->getUnsteadyLoadsImag(imd, ifq); + std::vector<Node *> nods = body->getNodes(); + for (size_t i = 0; i < nods.size(); ++i) + { + _clz1Re[imd][ifq](_rowsN.at(nods[i])) = clzre[i](2); + _clz1Im[imd][ifq](_rowsN.at(nods[i])) = clzim[i](2); + } + } + } + } + + // Surface gradient operator and average matrices + std::vector<sdpmMatrixXd> dN; + dN.resize(3, sdpmMatrixXd::Zero(_sol._rows.size(), _rowsN.size())); + for (auto p : _sol._rows) + { + Element *e = p.first; + std::vector<Node *> nodes = e->getNodes(); + sdpmMatrixXd dNe = e->computeGradientOperator(); + for (size_t in = 0; in < nodes.size(); ++in) + for (size_t idim = 0; idim < 3; ++idim) + dN[idim](_sol._rows.at(e), _rowsN.at(nodes[in])) = dNe(idim, in); + } + sdpmMatrixXd L = sdpmMatrixXd::Zero(_rowsN.size(), _sol._rows.size()); // elements-to-node average + for (auto body : _sol._pbl.getBodies()) + { + std::map<Node *, std::vector<Element *>> neMap = body->getMap(); + for (auto n : body->getNodes()) + for (auto e : neMap.at(n)) + L(_rowsN.at(n), _sol._rows.at(e)) = 1. / neMap.at(n).size(); + } + sdpmMatrixXd L1 = sdpmMatrixXd::Zero(_sol._rows.size(), _rowsN.size()); // nodes-to-element average + for (auto body : _sol._pbl.getBodies()) + for (auto e : body->getElements()) + for (auto n : e->getNodes()) + L1(_sol._rows.at(e), _rowsN.at(n)) = 1. / e->getNodes().size(); + + // Freestream values + sdpmDouble beta = _sol._pbl.getCompressibility(); // compressiblity factor + sdpmDouble mach = _sol._pbl.getMach(); // Mach number + sdpmVector3d uinf = _sol._pbl.getDragDir(); // velocity + std::vector<sdpmDouble> omega = _sol._pbl.getFrequencies(); // circular frequencies + // Geometry values + sdpmVector3d xRef = _sol._pbl.getRefCtr(); // reference center of rotation + // Organize panel and node data + sdpmMatrixXd cg = sdpmMatrixXd::Zero(_sol._nP, 3); // panel CG + sdpmMatrixXd s = sdpmVectorXd::Zero(_sol._nP); // panel area + sdpmMatrixXd n = sdpmMatrixXd::Zero(_sol._nP, 3); // panel compressible normal + sdpmMatrixXd nz = sdpmVectorXd::Zero(_sol._nP); // panel z-normal + sdpmMatrixXd u = sdpmMatrixXd::Zero(_sol._nP, 3); // steady velocity + sdpmVectorXd ux = sdpmVectorXd::Zero(_sol._nP); // steady perturbation x-velocity + for (auto body : _sol._pbl.getBodies()) + { + for (auto e : body->getElements()) + { + cg.row(_sol._rows.at(e)) = e->getCg(); + s(_sol._rows.at(e)) = e->getSurfaceArea(); + n.row(_sol._rows.at(e)) = e->getCompressibleNormal(); + nz(_sol._rows.at(e)) = e->getNormal()(2); + u.row(_sol._rows.at(e)) = _sol._u[e->getId() - 1]; + ux(_sol._rows.at(e)) = _sol._u[e->getId() - 1](0) - uinf(0); + } + } + + // Compute derivatives + std::cout << "Computing pressure derivatives... " << std::flush; + for (size_t ifq = 0; ifq < omega.size(); ++ifq) + { + // K matrices + sdpmMatrixXcd K = _sol._A[ifq].partialPivLu().inverse() * _sol._B[ifq]; + std::vector<sdpmMatrixXcd> Kx(3); + for (size_t idim = 0; idim < 3; ++idim) + Kx[idim] = dN[idim] * L * K; + + // Pressure stiffness terms (multiplied by 1) + // w.r.t angle of attack + sdpmVectorXcd cp_a = (2 / beta * u.col(0).array() - 2 * mach * mach / beta * ux.array()) * (uinf(0) * n.col(2).array() * n.col(0).array() + (Kx[0] * uinf(0) * n.col(2)).array()) + 2 * u.col(1).array() * (uinf(0) * n.col(2).array() * n.col(1).array() + (Kx[1] * uinf(0) * n.col(2)).array()) + 2 * u.col(2).array() * (-uinf(0) + uinf(0) * n.col(2).array() * n.col(2).array() + (Kx[2] * uinf(0) * n.col(2)).array()); + _dCp1Re_a[ifq] = cp_a.real(); + _dCp1Im_a[ifq] = cp_a.imag(); + // w.r.t modal x-rotations + sdpmMatrixXcd cp_rx = (2 / beta * u.col(0).asDiagonal() - 2 * mach * mach / beta * ux.asDiagonal()) * (sdpmMatrixXd((uinf(2) * n.col(1) - uinf(1) * n.col(2)).asDiagonal()) * n.col(0).asDiagonal() + Kx[0] * (uinf(2) * n.col(1) - uinf(1) * n.col(2)).asDiagonal()) + 2 * u.col(1).asDiagonal() * (-uinf(2) * sdpmMatrixXd::Identity(_sol._nP, _sol._nP) + sdpmMatrixXd((uinf(2) * n.col(1) - uinf(1) * n.col(2)).asDiagonal()) * n.col(1).asDiagonal() + Kx[1] * (uinf(2) * n.col(1) - uinf(1) * n.col(2)).asDiagonal()) + 2 * u.col(2).asDiagonal() * (uinf(1) * sdpmMatrixXd::Identity(_sol._nP, _sol._nP) + sdpmMatrixXd((uinf(2) * n.col(1) - uinf(1) * n.col(2)).asDiagonal()) * n.col(2).asDiagonal() + Kx[2] * (uinf(2) * n.col(1) - uinf(1) * n.col(2)).asDiagonal()); + _dCp1Re_rx[ifq] = cp_rx.real(); + _dCp1Im_rx[ifq] = cp_rx.imag(); + // w.r.t modal y-rotations + sdpmMatrixXcd cp_ry = -2 * u.col(0).asDiagonal() * uinf(2) * sdpmMatrixXd::Identity(_sol._nP, _sol._nP) + (2 / beta * u.col(0).asDiagonal() - 2 * mach * mach / beta * ux.asDiagonal()) * (sdpmMatrixXd((uinf(2) / beta * n.col(0) - uinf(0) * n.col(2)).asDiagonal()) * n.col(0).asDiagonal() + Kx[0] * (uinf(2) / beta * n.col(0) - uinf(0) * n.col(2)).asDiagonal()) + 2 * u.col(1).asDiagonal() * (sdpmMatrixXd((uinf(2) / beta * n.col(0) - uinf(0) * n.col(2)).asDiagonal()) * n.col(1).asDiagonal() + Kx[1] * (uinf(2) / beta * n.col(0) - uinf(0) * n.col(2)).asDiagonal()) + 2 * u.col(2).asDiagonal() * (uinf(0) * sdpmMatrixXd::Identity(_sol._nP, _sol._nP) + sdpmMatrixXd((uinf(2) / beta * n.col(0) - uinf(0) * n.col(2)).asDiagonal()) * n.col(2).asDiagonal() + Kx[2] * (uinf(2) / beta * n.col(0) - uinf(0) * n.col(2)).asDiagonal()); + _dCp1Re_ry[ifq] = cp_ry.real(); + _dCp1Im_ry[ifq] = cp_ry.imag(); + + // Pressure damping terms (multiplied by i*omega) + // w.r.t time derivative of angle of attack + sdpmVectorXcd cp_aDot = 2 * u.col(0).array() * (cg.col(2).array() - xRef(2)) + (2 / beta * u.col(0).array() - 2 * mach * mach / beta * ux.array()) * ((-(cg.col(2).array() - xRef(2)) / beta * n.col(0).array() + (cg.col(0).array() - xRef(0)) * n.col(2).array()) * n.col(0).array() + (Kx[0] * (-(cg.col(2).array() - xRef(2)) / beta * n.col(0).array() + (cg.col(0).array() - xRef(0)) * n.col(2).array()).matrix()).array()) + 2 * u.col(1).array() * ((-(cg.col(2).array() - xRef(2)) / beta * n.col(0).array() + (cg.col(0).array() - xRef(0)) * n.col(2).array()) * n.col(1).array() + (Kx[1] * (-(cg.col(2).array() - xRef(2)) / beta * n.col(0).array() + (cg.col(0).array() - xRef(0)) * n.col(2).array()).matrix()).array()) + 2 * u.col(2).array() * (-(cg.col(0).array() - xRef(0)) + (-(cg.col(2).array() - xRef(2)) / beta * n.col(0).array() + (cg.col(0).array() - xRef(0)) * n.col(2).array()) * n.col(2).array() + (Kx[2] * (-(cg.col(2).array() - xRef(2)) / beta * n.col(0).array() + (cg.col(0).array() - xRef(0)) * n.col(2).array()).matrix()).array()) + 2 * (K * uinf(0) * n.col(2)).array() * (1 - mach * mach * ux.array()); + cp_aDot *= sdpmComplex(0., omega[ifq]); + _dCp1Re_aDot[ifq] = cp_aDot.real(); + _dCp1Im_aDot[ifq] = cp_aDot.imag(); + // w.r.t time derivative of z-displacement + sdpmVectorXcd cp_hDot = (2 / beta * u.col(0).array() - 2 * mach * mach / beta * ux.array()) * (n.col(2).array() * n.col(0).array() + (Kx[0] * n.col(2)).array()) + 2 * u.col(1).array() * (n.col(2).array() * n.col(1).array() + (Kx[1] * n.col(2)).array()) + 2 * u.col(2).array() * (-1 + n.col(2).array() * n.col(2).array() + (Kx[2] * n.col(2)).array()); + cp_hDot *= sdpmComplex(0., omega[ifq]); + _dCp1Re_hDot[ifq] = cp_hDot.real(); + _dCp1Im_hDot[ifq] = cp_hDot.imag(); + // w.r.t time derivative of modal x-rotations + sdpmMatrixXcd cp_rxDot = sdpmComplex(0., 2 * omega[ifq]) * (sdpmMatrixXd::Identity(_sol._nP, _sol._nP) - sdpmMatrixXd(mach * mach * ux.asDiagonal())) * (K * (uinf(2) * n.col(1) - uinf(1) * n.col(2)).asDiagonal()); + _dCp1Re_rxDot[ifq] = cp_rxDot.real(); + _dCp1Im_rxDot[ifq] = cp_rxDot.imag(); + // w.r.t time derivative of modal y-rotations + sdpmMatrixXcd cp_ryDot = sdpmComplex(0., 2 * omega[ifq]) * (sdpmMatrixXd::Identity(_sol._nP, _sol._nP) - sdpmMatrixXd(mach * mach * ux.asDiagonal())) * (K * (uinf(2) / beta * n.col(0) - uinf(0) * n.col(2)).asDiagonal()); + _dCp1Re_ryDot[ifq] = cp_ryDot.real(); + _dCp1Im_ryDot[ifq] = cp_ryDot.imag(); + // w.r.t time derivative of modal z-displacements + sdpmMatrixXcd cp_dzDot = (2 / beta * u.col(0).asDiagonal() - 2 * mach * mach / beta * ux.asDiagonal()) * (sdpmMatrixXd(n.col(2).asDiagonal()) * n.col(0).asDiagonal() + Kx[0] * n.col(2).asDiagonal()) + 2 * u.col(1).asDiagonal() * (sdpmMatrixXd(n.col(2).asDiagonal()) * n.col(1).asDiagonal() + Kx[1] * n.col(2).asDiagonal()) + 2 * u.col(2).asDiagonal() * (-sdpmMatrixXd::Identity(_sol._nP, _sol._nP) + sdpmMatrixXd(n.col(2).asDiagonal()) * n.col(2).asDiagonal() + Kx[2] * n.col(2).asDiagonal()); + cp_dzDot *= sdpmComplex(0., omega[ifq]); + _dCp1Re_dzDot[ifq] = cp_dzDot.real(); + _dCp1Im_dzDot[ifq] = cp_dzDot.imag(); + + // Pressure inertia terms (multiplied by (i*omega)^2) + // w.r.t second time derivative of angle of attack + sdpmVectorXcd cp_aDotDot = -2 * omega[ifq] * omega[ifq] * (1 - mach * mach * ux.array()) * (K * (-(cg.col(2).array() - xRef(2)) / beta * n.col(0).array() + (cg.col(0).array() - xRef(0)) * n.col(2).array()).matrix()).array(); + _dCp1Re_aDotDot[ifq] = cp_aDotDot.real(); + _dCp1Im_aDotDot[ifq] = cp_aDotDot.imag(); + // w.r.t second time derivative of z-displacement + sdpmVectorXcd cp_hDotDot = -2 * omega[ifq] * omega[ifq] * (1 - mach * mach * ux.array()) * (K * n.col(2)).array(); + _dCp1Re_hDotDot[ifq] = cp_hDotDot.real(); + _dCp1Im_hDotDot[ifq] = cp_hDotDot.imag(); + // w.r.t second time derivative of modal z-displacements + sdpmMatrixXcd cp_dzDotDot = -2 * omega[ifq] * omega[ifq] * (sdpmMatrixXd::Identity(_sol._nP, _sol._nP) - sdpmMatrixXd(mach * mach * ux.asDiagonal())) * (K * n.col(2).asDiagonal()); + _dCp1Re_dzDotDot[ifq] = cp_dzDotDot.real(); + _dCp1Im_dzDotDot[ifq] = cp_dzDotDot.imag(); + + // Load terms + sdpmMatrixXcd clz_rx = L1.transpose() * s.asDiagonal() * nz.asDiagonal() * (cp_rx + cp_rxDot) * L1; + _dClz1Re_rx[ifq] = clz_rx.real(); + _dClz1Im_rx[ifq] = clz_rx.imag(); + sdpmMatrixXcd clz_ry = L1.transpose() * s.asDiagonal() * nz.asDiagonal() * (cp_ry + cp_ryDot) * L1; + _dClz1Re_ry[ifq] = clz_ry.real(); + _dClz1Im_ry[ifq] = clz_ry.imag(); + sdpmMatrixXcd clz_dz = L1.transpose() * s.asDiagonal() * nz.asDiagonal() * (cp_dzDot + cp_dzDotDot) * L1; + _dClz1Re_dz[ifq] = clz_dz.real(); + _dClz1Im_dz[ifq] = clz_dz.imag(); + } + std::cout << "done." << std::endl; +} + +/** + * @brief Compute partial derivative of real part of GAF matrix entry at given frequency with respect to given mode + */ +std::vector<sdpmDouble> Gradient::computePartialsGafRe(size_t ifq, int imd, int irw, int jcl, std::string const &wrt) +{ + // Check input + sdpmMatrixXd dClz1; + if (wrt == "rx") + dClz1 = _dClz1Re_rx[ifq]; + else if (wrt == "ry") + dClz1 = _dClz1Re_ry[ifq]; + else if (wrt == "dz") + dClz1 = _dClz1Re_dz[ifq]; + else + throw std::runtime_error("sdpm::Gradient::computePartialsGafRe: wrt parameter should be either 'rx', 'ry' or 'dz'!\n"); + + // Compute derivative + sdpmVectorXd dq = sdpmVectorXd::Zero(_nN); + if (jcl == imd) + dq += dClz1.transpose() * _dz[irw]; + if (irw == imd && wrt == "dz") + dq -= _clz1Re[jcl][ifq]; + return std::vector<sdpmDouble>(dq.data(), dq.data() + dq.size()); +} + +/** + * @brief Compute partial derivative of imaginary part of GAF matrix entry at given frequency with respect to given mode + */ +std::vector<sdpmDouble> Gradient::computePartialsGafIm(size_t ifq, int imd, int irw, int jcl, std::string const &wrt) +{ + // Check input + sdpmMatrixXd dClz1; + if (wrt == "rx") + dClz1 = _dClz1Im_rx[ifq]; + else if (wrt == "ry") + dClz1 = _dClz1Im_ry[ifq]; + else if (wrt == "dz") + dClz1 = _dClz1Im_dz[ifq]; + else + throw std::runtime_error("sdpm::Gradient::computePartialsGafIm: wrt parameter should be either 'rx', 'ry' or 'dz'!\n"); + + // Compute derivative + sdpmVectorXd dq = sdpmVectorXd::Zero(_nN); + if (jcl == imd) + dq += dClz1.transpose() * _dz[irw]; + if (irw == imd && wrt == "dz") + dq -= _clz1Im[jcl][ifq]; + return std::vector<sdpmDouble>(dq.data(), dq.data() + dq.size()); +} + +/** + * @brief Test pressure derivatives by mulitplying with DOF and comparing to pressure + * @todo remove + */ +void Gradient::test(double a_amp, double h_amp) +{ + sdpmVectorXd cp1re = (_dCp1Re_a[0] + _dCp1Re_aDot[0] + _dCp1Re_aDotDot[0]) * a_amp + (_dCp1Re_hDot[0] + _dCp1Re_hDotDot[0]) * h_amp; + sdpmVectorXd cp1im = (_dCp1Im_a[0] + _dCp1Im_aDot[0] + _dCp1Im_aDotDot[0]) * a_amp + (_dCp1Im_hDot[0] + _dCp1Im_hDotDot[0]) * h_amp; + for (auto b : _sol._pbl.getBodies()) + { + for (auto e : b->getElements()) + { + sdpmDouble deltaCp1Re = _sol._cp1Re[0][0][e->getId() - 1] - cp1re(_sol._rows.at(e)); + sdpmDouble deltaCp1Im = _sol._cp1Im[0][0][e->getId() - 1] - cp1im(_sol._rows.at(e)); + if (abs(deltaCp1Re) > 1e-12 || abs(deltaCp1Im) > 1e-12) + throw std::runtime_error("sdpm::Gradient::test() failed!\n"); + } + } +} + +void Gradient::write(std::ostream &out) const +{ + out << "sdpm::Gradient on\n" + << _sol; +} diff --git a/sdpm/src/sdpmGradient.h b/sdpm/src/sdpmGradient.h new file mode 100644 index 0000000000000000000000000000000000000000..4da66726d476d1d86ae45eb6305c7d94d195b8a6 --- /dev/null +++ b/sdpm/src/sdpmGradient.h @@ -0,0 +1,97 @@ +/* + * Copyright 2023 University of Liège + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SDPMGRADIENT_H +#define SDPMGRADIENT_H + +#include "sdpm.h" +#include "sdpmObject.h" +#include <vector> +#include <map> + +namespace sdpm +{ + +/** + * @brief Source-doublet panel gradient interface + * @authors Adrien Crovato + * @todo remove dummy test class once rigid motions (a and h) have been included in GAF matrix computation + */ +class SDPM_API Gradient : public sdpmObject +{ + Solver &_sol; ///< SDPM solver + size_t _nF; ///< number of frequencies + size_t _nM; ///< number of modes + size_t _nN; ///< number of nodes + std::map<Node *, int> _rowsN; ///< node row identification map + + // Displacements and loads + std::vector<sdpmVectorXd> _dz; ///< z-displacement at nodes + std::vector<std::vector<sdpmVectorXd>> _clz1Re; ///< z-component of real part of pressure load at nodes + std::vector<std::vector<sdpmVectorXd>> _clz1Im; ///< z-component of real imaginary of pressure load at nodes + + // Pressure derivatives (stiffness terms) + std::vector<sdpmVectorXd> _dCp1Re_a; ///< gradient of real part of unsteady pressure w.r.t. angle of attack + std::vector<sdpmVectorXd> _dCp1Im_a; ///< gradient of imaginary part of unsteady pressure w.r.t. angle of attack + std::vector<sdpmMatrixXd> _dCp1Re_rx; ///< gradient of real part of unsteady pressure w.r.t. modal rotations about x + std::vector<sdpmMatrixXd> _dCp1Im_rx; ///< gradient of imaginary part of unsteady pressure w.r.t. modal rotations about x + std::vector<sdpmMatrixXd> _dCp1Re_ry; ///< gradient of real part of unsteady pressure w.r.t. modal rotations about y + std::vector<sdpmMatrixXd> _dCp1Im_ry; ///< gradient of imaginary part of unsteady pressure w.r.t. modal rotations about y + // Pressure derivatives (damping terms) + std::vector<sdpmVectorXd> _dCp1Re_aDot; ///< gradient of real part of unsteady pressure w.r.t. time derivative of angle of attack + std::vector<sdpmVectorXd> _dCp1Im_aDot; ///< gradient of imaginary part of unsteady pressure w.r.t. time derivative of angle of attack + std::vector<sdpmVectorXd> _dCp1Re_hDot; ///< gradient of real part of unsteady pressure w.r.t. time derivative of vertical displacement + std::vector<sdpmVectorXd> _dCp1Im_hDot; ///< gradient of imaginary part of unsteady pressure w.r.t. time derivative of vertical displacement + std::vector<sdpmMatrixXd> _dCp1Re_rxDot; ///< gradient of real part of unsteady pressure w.r.t. time derivative of modal rotations about x + std::vector<sdpmMatrixXd> _dCp1Im_rxDot; ///< gradient of imaginary part of unsteady pressure w.r.t. time derivative of modal rotations about x + std::vector<sdpmMatrixXd> _dCp1Re_ryDot; ///< gradient of real part of unsteady pressure w.r.t. time derivative of modal rotations about y + std::vector<sdpmMatrixXd> _dCp1Im_ryDot; ///< gradient of imaginary part of unsteady pressure w.r.t. time derivative of modal rotations about y + std::vector<sdpmMatrixXd> _dCp1Re_dzDot; ///< gradient of real part of unsteady pressure w.r.t. time derivative of modal displacements along z + std::vector<sdpmMatrixXd> _dCp1Im_dzDot; ///< gradient of imaginary part of unsteady pressure w.r.t. time derivative of modal displacements along z + // Pressure derivatives (inertia terms) + std::vector<sdpmVectorXd> _dCp1Re_aDotDot; ///< gradient of real part of unsteady pressure w.r.t. second time derivative of angle of attack + std::vector<sdpmVectorXd> _dCp1Im_aDotDot; ///< gradient of imaginary part of unsteady pressure w.r.t. second time derivative of angle of attack + std::vector<sdpmVectorXd> _dCp1Re_hDotDot; ///< gradient of real part of unsteady pressure w.r.t. second time derivative of vertical displacement + std::vector<sdpmVectorXd> _dCp1Im_hDotDot; ///< gradient of imaginary part of unsteady pressure w.r.t. second time derivative of vertical displacement + std::vector<sdpmMatrixXd> _dCp1Re_dzDotDot; ///< gradient of real part of unsteady pressure w.r.t. second time derivative of modal displacements along z + std::vector<sdpmMatrixXd> _dCp1Im_dzDotDot; ///< gradient of imaginary part of unsteady pressure w.r.t. second time derivative of modal displacements along z + + // Loads derivatives + std::vector<sdpmMatrixXd> _dClz1Re_rx; ///< gradient of real part of unsteady loads z-component w.r.t. modal rotations about x + std::vector<sdpmMatrixXd> _dClz1Im_rx; ///< gradient of imaginary part of unsteady loads z-component w.r.t. modal rotations about x + std::vector<sdpmMatrixXd> _dClz1Re_ry; ///< gradient of real part of unsteady loads z-component w.r.t. modal rotations about y + std::vector<sdpmMatrixXd> _dClz1Im_ry; ///< gradient of imaginary part of unsteady loads z-component w.r.t. modal rotations about y + std::vector<sdpmMatrixXd> _dClz1Re_dz; ///< gradient of real part of unsteady loads z-component w.r.t. modal displacements along z + std::vector<sdpmMatrixXd> _dClz1Im_dz; ///< gradient of imaginary part of unsteady loads z-component w.r.t. modal displacements along z + +public: + Gradient(Solver &sol); + ~Gradient(); + + void run(); + std::vector<sdpmDouble> computePartialsGafRe(size_t ifq, int imd, int irw, int jcl, std::string const &wrt); + std::vector<sdpmDouble> computePartialsGafIm(size_t ifq, int imd, int irw, int jcl, std::string const &wrt); + + void test(double a_amp, double h_amp); + +#ifndef SWIG + virtual void write(std::ostream &out) const override; +#endif +}; + +} // namespace sdpm + +#endif // SDPMGRADIENT_H diff --git a/sdpm/src/sdpmProblem.h b/sdpm/src/sdpmProblem.h index 0b7022e37bf8f4f3fa71c45546d4abae460cbb61..046cfe41ab27746314c6883496c6b7636a8d4867 100644 --- a/sdpm/src/sdpmProblem.h +++ b/sdpm/src/sdpmProblem.h @@ -31,7 +31,8 @@ namespace sdpm */ class SDPM_API Problem : public sdpmObject { - friend class Adjoint; // so that Adjoint can access the problem inputs + friend class Adjoint; // so that Adjoint can access the problem inputs + friend class Gradient; // so that Gradient can access the problem outputs // Mesh Mesh &_msh; ///< mesh data structure diff --git a/sdpm/src/sdpmSolver.h b/sdpm/src/sdpmSolver.h index ab5cc32eb10585b3df7a5e30664b7230fff81ba6..fa2c5ff833e8d0f5da3e8ad03741d31e146cbfb8 100644 --- a/sdpm/src/sdpmSolver.h +++ b/sdpm/src/sdpmSolver.h @@ -31,7 +31,8 @@ namespace sdpm */ class SDPM_API Solver : public sdpmObject { - friend class Adjoint; // so that Adjoint can access the solver outputs + friend class Adjoint; // so that Adjoint can access the solver outputs + friend class Gradient; // so that Gradient can access the solver outputs protected: // Problem diff --git a/sdpm/tests/agard_grad.py b/sdpm/tests/agard_grad.py new file mode 100644 index 0000000000000000000000000000000000000000..7d06d44285d17337fe8a6167dea63c92684ab11b --- /dev/null +++ b/sdpm/tests/agard_grad.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +# Copyright 2023 University of Liège +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# AGARD 445.6 wing (gradient) +# Adrien Crovato + +import sdpm +from sdpm.gmsh import MeshLoader +from sdpm.utils import build_fpath, interpolate_modes +from sdpm.testing import * +import numpy as np + +def main(): + # geometry parameters + wnc = 20 # number of chordwise panels + pars = {'nC': wnc, 'nS': 10, 'pC': 1.1, 'pS': 1.0} + sref = 0.353 # reference area + crd = 0.559 # root chord + # flow and time parameters + minf = 0.9 # freestream Mach number + aoa = 1.0 * np.pi / 180 # angle of attack + kred = 0.1 # reduced frequency + n_modes = 4 # number of modes + + # start timer + tms = sdpm.Timers() + tms['total'].start() + # mesh + tms['msh'].start() + msh = MeshLoader(build_fpath('models/agard445.geo', __file__, 1)).run(pars) + tms['msh'].stop() + # problem + tms['pbl'].start() + pbl = sdpm.Problem(msh) + pbl.setGeometry(sref, crd, 0., 0., 0., True) + pbl.setFreestream(aoa, 0., minf) + pbl.setUnsteady(n_modes, [kred]) + wing = sdpm.Body(msh, 'wing', 'te', crd, wnc, n_modes, 1) + x_modes, amp_modes = interpolate_modes(build_fpath('models/agard445_modes.csv', __file__, 1), wing.getNodes()) + for i in range(n_modes): + wing.addMotion() + wing.setFlexibleMotion(i, 1 / amp_modes[i], x_modes[:, 3*i:3*i+3]) + pbl.addBody(wing) + tms['pbl'].stop() + # solver + tms['sol'].start() + sol = sdpm.Solver(pbl) + sol.update() + sol.run() + sol.save(sdpm.GmshExport(msh)) + tms['sol'].stop() + # gradient evaluation + tms['grd'].start() + grd = sdpm.Gradient(sol) + grd.run() + tms['grd'].stop() + # end timer + tms['total'].stop() + print(tms) + + # recover GAF matrix from partial gradients (NB: since GAF is quadratic in DOF, result must be divided by 2) + q_re = np.array(sol.getGafMatrixRe(0), dtype=float) + q_im = np.array(sol.getGafMatrixIm(0), dtype=float) + dq_re = np.zeros((n_modes, n_modes), dtype=float) + dq_im = np.zeros((n_modes, n_modes), dtype=float) + for i in range(n_modes): + for j in range(n_modes): + dq_re[i,j] = np.array(grd.computePartialsGafRe(0, j, i, j, 'dz'), dtype=float).dot(x_modes[:, 3*j+0] / amp_modes[j]) \ + + np.array(grd.computePartialsGafRe(0, j, i, j, 'rx'), dtype=float).dot(x_modes[:, 3*j+1] / amp_modes[j]) \ + + np.array(grd.computePartialsGafRe(0, j, i, j, 'ry'), dtype=float).dot(x_modes[:, 3*j+2] / amp_modes[j]) + dq_im[i,j] = np.array(grd.computePartialsGafIm(0, j, i, j, 'dz'), dtype=float).dot(x_modes[:, 3*j+0] / amp_modes[j]) \ + + np.array(grd.computePartialsGafIm(0, j, i, j, 'rx'), dtype=float).dot(x_modes[:, 3*j+1] / amp_modes[j]) \ + + np.array(grd.computePartialsGafIm(0, j, i, j, 'ry'), dtype=float).dot(x_modes[:, 3*j+2] / amp_modes[j]) + if i != j: + dq_re[i,j] += np.array(grd.computePartialsGafRe(0, i, i, j, 'dz'), dtype=float).dot(x_modes[:, 3*i] / amp_modes[i]) + dq_im[i,j] += np.array(grd.computePartialsGafIm(0, i, i, j, 'dz'), dtype=float).dot(x_modes[:, 3*i] / amp_modes[i]) + dq_re *= 0.5 + dq_im *= 0.5 + + # test + tests = CTests() + tests.add(CTest('Re(Q)',np.linalg.norm(q_re - dq_re), 0.0, 1e-10, forceabs=True)) + tests.add(CTest('Im(Q)',np.linalg.norm(q_im - dq_im), 0.0, 1e-10, forceabs=True)) + tests.run() + + # eof + print('') + +if __name__ == '__main__': + main() diff --git a/sdpm/tests/lann_grad.py b/sdpm/tests/lann_grad.py new file mode 100644 index 0000000000000000000000000000000000000000..85d5e335932f8104a71b333c9bda59c2e03c8b0a --- /dev/null +++ b/sdpm/tests/lann_grad.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +# Copyright 2023 University of Liège +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# LANN wing (gradient) +# Adrien Crovato + +import sdpm +from sdpm.gmsh import MeshLoader +from sdpm.utils import build_fpath +from sdpm.testing import * +import math + +def main(): + # geometry parameters + wnc = 20 # number of chordwise panels + pars = {'nC': wnc, 'bC': 0.25, 'rS': 1} + sref = 0.253 # reference area + crd = 0.361 # root chord + xf = 0.224 # x-coordinate of rotation center + # flow and time parameters + minf = 0.621 # freestream Mach number + aoa = 0.59 * math.pi / 180 + kred = 0.133 # reduced frequency + a_amp = 0.5 * 0.25 * math.pi / 180 # semi pitching amplitude + h_amp = 0.1 # plunging amplitude + + # start timer + tms = sdpm.Timers() + tms['total'].start() + # mesh + tms['msh'].start() + msh = MeshLoader(build_fpath('models/lann.geo', __file__, 1)).run(pars) + tms['msh'].stop() + # problem + tms['pbl'].start() + pbl = sdpm.Problem(msh) + pbl.setGeometry(sref, crd, xf, 0., 0., True) + pbl.setFreestream(aoa, 0., minf) + pbl.setUnsteady(1, [kred]) + wing = sdpm.Body(msh, 'wing', 'te', crd, wnc, 1, 1) + wing.addMotion() + wing.setRigidMotion(0, a_amp, h_amp, xf, 0.) + pbl.addBody(wing) + tms['pbl'].stop() + # solver + tms['sol'].start() + sol = sdpm.Solver(pbl) + sol.update() + sol.run() + sol.save(sdpm.GmshExport(msh)) + tms['sol'].stop() + # gradient evaluation + tms['grd'].start() + grd = sdpm.Gradient(sol) + grd.run() + tms['grd'].stop() + # end timer + tms['total'].stop() + print(tms) + + # test + grd.test(a_amp, h_amp) # TODO, replace by GAF matrix gradient check once implemented + tests = CTests() + tests.run() + + # eof + print('') + +if __name__ == '__main__': + main()