From f906666d1e483922e6569f96f2e77d57445c5b7b Mon Sep 17 00:00:00 2001 From: Florian Reetz Date: Thu, 7 May 2020 12:29:34 +0200 Subject: [PATCH 01/10] Created the modules folder and modified cmakelists to offer the build option for the ilc module. Added the ilcflags to the module. --- CMakeLists.txt | 14 ++++ modules/ilc/CMakeLists.txt | 26 +++++++ modules/ilc/ilcflags.cpp | 140 +++++++++++++++++++++++++++++++++++++ modules/ilc/ilcflags.h | 63 +++++++++++++++++ 4 files changed, 243 insertions(+) create mode 100644 modules/ilc/CMakeLists.txt create mode 100644 modules/ilc/ilcflags.cpp create mode 100644 modules/ilc/ilcflags.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e2bdfa7c..cad2fdc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ OPTION(USE_MPI "use mpi, if found" ON) # use -DUSE_MPI=off to force disable use OPTION(BUILD_SHARED_LIBS "Builds shared libraries if ON, static libraries if OFF" ON) OPTION(SET_RPATH "set the rpath of the executable to the found libraries" ON) OPTION(WITH_NSOLVER "compile support for the nsolver module" ON) +OPTION(WITH_ILC "compile programs for inclined layer convection" OFF) OPTION(WITH_PYTHON "build a python wrapper, using boost-python" OFF) OPTION(WITH_HDF5CXX "enable legacy file format using hdf5 cxx" OFF) OPTION(WITH_GTEST "enable gtest unit testing" ON) @@ -190,6 +191,13 @@ add_subdirectory(programs) add_subdirectory(tools) add_subdirectory(examples) +if (WITH_ILC) + if (NOT WITH_NSOLVER) + message(FATAL_ERROR "Compiling the ILC module requires -DWITH_NSOLVER=ON") + endif() + add_subdirectory(modules/ilc) +endif () + # Check if we want to build the python wrapper and have boost-python # If Python and boost.python are there, build python interface if (WITH_PYTHON) @@ -292,6 +300,12 @@ else () message(" nsolver: disabled") endif () +if (WITH_ILC) + message(" Inclined layer convection: enabled") +else () + message(" Inclined layer convection: disabled") +endif () + if (WITH_PYTHON) message(" Python wrapper: enabled") else () diff --git a/modules/ilc/CMakeLists.txt b/modules/ilc/CMakeLists.txt new file mode 100644 index 00000000..7492d278 --- /dev/null +++ b/modules/ilc/CMakeLists.txt @@ -0,0 +1,26 @@ +# +# This file is a part of channelflow version 2.0 https://channelflow.ch. +# License is GNU GPL version 2 or later: ./LICENCE +# +# Include and configure the Inclined Layer Convection module of channelflow. +# +set( + ilc_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/ilcflags.cpp +) + +set( + ilc_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/ilcflags.h +) + +# Define the target with appropriate dependencies +install_channelflow_library(ilc) +target_link_fftw(ilc PRIVATE) +target_link_libraries(ilc PUBLIC chflow) +if (WITH_NSOLVER) + target_link_libraries(ilc PUBLIC nsolver) +endif () + +# Install header files +install(FILES ${ilc_HEADERS} DESTINATION include/modules/ilc) diff --git a/modules/ilc/ilcflags.cpp b/modules/ilc/ilcflags.cpp new file mode 100644 index 00000000..e0073d11 --- /dev/null +++ b/modules/ilc/ilcflags.cpp @@ -0,0 +1,140 @@ +/** + * This file is a part of channelflow version 2.0, https://channelflow.ch . + * License is GNU GPL version 2 or later: ./LICENSE + * + * Original author: Florian Reetz + */ + +#include "modules/ilc/ilcflags.h" + +namespace chflow { + +ILCFlags::ILCFlags(Real nu_, Real kappa_, Real alpha_, Real grav_, Real rho_ref_, Real t_ref_, Real gammax_, + Real gammaz_, Real ulowerwall_, Real uupperwall_, Real wlowerwall_, Real wupperwall_, + Real tlowerwall_, Real tupperwall_, Real ystats_) + : kappa(kappa_), + alpha(alpha_), + grav(grav_), + rho_ref(rho_ref_), + t_ref(t_ref_), + gammax(gammax_), + gammaz(gammaz_), + tlowerwall(tlowerwall_), + tupperwall(tupperwall_), + ystats(ystats_) { + nu = nu_; + ulowerwall = ulowerwall_; + uupperwall = uupperwall_; + wlowerwall = wlowerwall_; + wupperwall = wupperwall_; + + // freefall = sqrt(grav*alpha*(tlowerwall-tupperwall)); + // Treference = 0.5*(tlowerwall+tupperwall); +} + +ILCFlags::ILCFlags(ArgList& args, const bool laurette) { + // ILC system parameters + args.section("System parameters"); + const Real Rayleigh_ = args.getreal("-Ra", "--Rayleigh", 4000, "pseudo-Rayleigh number == 1/(nu kp)"); + const Real Prandtl_ = args.getreal("-Pr", "--Prandtl", 1, "Prandtl number == nu/kp"); + const Real gammax_ = args.getreal("-gx", "--gammax", 45, "inclination angle of x-dim in deg"); + const Real gammaz_ = args.getreal("-gz", "--gammaz", 0, "inclination angle of z-dim in deg"); + const Real nuarg_ = + args.getreal("-nu", "--nu", 1, "kinematic viscosity (takes precedence, with kp, over Ra and Pr, if nonunity)"); + const Real kparg_ = args.getreal("-kp", "--kappa", 1, + "thermal conductivity (takes precedence, with nu, over Ra and Pr, if nonunity)"); + + // define Channelflow boundary conditions from arglist + args2BC(args); + + // add ILC boundary conditions + const Real deltaT_ = + args.getreal("-delT", "--DeltaT", 1, "temperature difference between the walls: DeltaT = T_lower - T_upper"); + const Real Tref_ = args.getreal("-Tref", "--Treference", 0, + "reference temperature of the surrounding fluid reservoir for thermal expansion"); + const Real grav_ = args.getreal( + "-grav", "--gravity", 1.0, + "gravity coupling of the velocity fluctuations to the temperature fluctuations (no change of base or Ra)"); + const Real ilcUwall_ = + args.getreal("-ilcUw", "--ilcUwall", 0.0, "in ILC, magnitude of imposed wall velocity, +/-Uwall at y = +/-h"); + + // define Channelflow numerics from arglist + args2numerics(args, laurette); + + // also needed for ILC but better show at the end + const std::string tsymmstr = args.getstr("-tsymms", "--tempsymmetries", "", + "constrain temp(t) to invariant " + "symmetric subspace, argument is the filename for a file " + "listing the generators of the isotropy group"); + const Real ystats_ = args.getreal("-ys", "--ystats", 0, "y-coordinate of height dependent statistics, e.g. Nu(y)"); + + // set flags + nu = (nuarg_ != 1) ? nuarg_ : sqrt(Prandtl_ / Rayleigh_); + kappa = (kparg_ != 1) ? kparg_ : 1.0 / sqrt(Prandtl_ * Rayleigh_); + gammax = gammax_ / 180.0 * pi; + gammaz = gammaz_ / 180.0 * pi; + t_ref = Tref_; + tlowerwall = 0.5 * deltaT_; + tupperwall = -0.5 * deltaT_; + grav = grav_; + ystats = ystats_; + + // overwrite the wall velocity with ilcUwall flag + Uwall = ilcUwall_; + ulowerwall = -ilcUwall_ * cos(theta); + uupperwall = ilcUwall_ * cos(theta); + wlowerwall = -ilcUwall_ * sin(theta); + wupperwall = ilcUwall_ * sin(theta); + + if (tsymmstr.length() > 0) { + SymmetryList tsymms(tsymmstr); + tempsymmetries = tsymms; + } + + save(); +} + +void ILCFlags::save(const std::string& savedir) const { + DNSFlags::save(savedir); + if (mpirank() == 0) { + std::string filename = appendSuffix(savedir, "ilcflags.txt"); + std::ofstream os(filename.c_str()); + if (!os.good()) + cferror("ILCFlags::save(savedir) : can't open file " + filename); + os.precision(16); + os.setf(std::ios::left); + os << std::setw(REAL_IOWIDTH) << kappa << " %kappa\n" + << std::setw(REAL_IOWIDTH) << gammax << " %gammax\n" + << std::setw(REAL_IOWIDTH) << gammaz << " %gammaz\n" + << std::setw(REAL_IOWIDTH) << alpha << " %alpha\n" + << std::setw(REAL_IOWIDTH) << grav << " %grav\n" + << std::setw(REAL_IOWIDTH) << rho_ref << " %rho_ref\n" + << std::setw(REAL_IOWIDTH) << t_ref << " %t_ref\n" + << std::setw(REAL_IOWIDTH) << tupperwall << " %tupperwall\n" + << std::setw(REAL_IOWIDTH) << tlowerwall << " %tlowerwall\n" + << std::setw(REAL_IOWIDTH) << ystats << " %ystats\n"; + os.unsetf(std::ios::left); + } +} + +void ILCFlags::load(int taskid, const std::string indir) { + DNSFlags::load(taskid, indir); + std::ifstream is; + if (taskid == 0) { + is.open(indir + "ilcflags.txt"); + if (!is.good()) + cferror(" ILCFlags::load(taskid, flags, dt, indir): can't open file " + indir + "ilcflags.txt"); + } + kappa = getRealfromLine(taskid, is); + gammax = getRealfromLine(taskid, is); + gammaz = getRealfromLine(taskid, is); + alpha = getRealfromLine(taskid, is); + grav = getRealfromLine(taskid, is); + rho_ref = getRealfromLine(taskid, is); + t_ref = getRealfromLine(taskid, is); + tupperwall = getRealfromLine(taskid, is); + tlowerwall = getRealfromLine(taskid, is); + ystats = getRealfromLine(taskid, is); +} + +} // namespace channelflow diff --git a/modules/ilc/ilcflags.h b/modules/ilc/ilcflags.h new file mode 100644 index 00000000..c74f6589 --- /dev/null +++ b/modules/ilc/ilcflags.h @@ -0,0 +1,63 @@ +/** + * Control parameters for time-integration within the ILC module + * ILCFlags specifies all relevant parameters for integrating the Oberbeck-Boussinesq equations in doubly periodic channel domains. + * + * This file is a part of channelflow version 2.0, https://channelflow.ch . + * License is GNU GPL version 2 or later: ./LICENSE + * + * Original author: Florian Reetz + */ + +#ifndef ILCFLAGS_H +#define ILCFLAGS_H + +#include "channelflow/dnsflags.h" +#include "channelflow/utilfuncs.h" + +namespace chflow { + +/** \brief extension of the DNSFlags class for ILC + * + * ILCFlags class, holds all additional parameters for convective shear flows + */ +class ILCFlags : public DNSFlags { + // Is derived from DNSFlags to keep saving and loading consistent + + public: + ILCFlags(Real nu = 0.025, // values are just below primary thermal instability + Real kappa = 0.025, Real alpha = 1.0, Real grav = 1.0, Real rho_ref = 1.0, Real t_ref = 0.0, + Real gammax = 0.0, Real gammaz = 0.0, Real ulowerwall = 0.0, Real uupperwall = 0.0, Real wlowerwall = 0.0, + Real wupperwall = 0.0, Real tlowerwall = 0.5, Real tupperwall = -0.5, Real ystats = 0); + // ILCFlags (const ILCFlags& ilcflags); + // ILCFlags (const DNSFlags& flags); + // ILCFlags (const std::string& filebase); + ILCFlags(ArgList& args, const bool laurette = false); + + /** \brief The infamous virtual destructor */ + virtual ~ILCFlags() = default; + + Real kappa; + Real alpha; + Real grav; + Real rho_ref; + Real t_ref; + Real gammax; + Real gammaz; + + Real tlowerwall; + Real tupperwall; + + Real ystats; + + // Real freefall; + // Real Treference; + + cfarray tempsymmetries; // restrict temp(t) to these symmetries + + // override DNSFlags::save/load. Both methods include a call to the parent method + virtual void save(const std::string& savedir = "") const override; + virtual void load(int taskid, const std::string indir) override; +}; + +} // namespace channelflow +#endif // ILCFLAGS_H \ No newline at end of file From e2254c80c171ad06712bd17ca27ef89b328ab72b Mon Sep 17 00:00:00 2001 From: Florian Reetz Date: Thu, 7 May 2020 13:13:33 +0200 Subject: [PATCH 02/10] Added the implementation of the Oberbeck-Boussinesq equations --- modules/ilc/CMakeLists.txt | 2 + modules/ilc/obe.cpp | 810 +++++++++++++++++++++++++++++++++++++ modules/ilc/obe.h | 105 +++++ 3 files changed, 917 insertions(+) create mode 100644 modules/ilc/obe.cpp create mode 100644 modules/ilc/obe.h diff --git a/modules/ilc/CMakeLists.txt b/modules/ilc/CMakeLists.txt index 7492d278..2fb94b11 100644 --- a/modules/ilc/CMakeLists.txt +++ b/modules/ilc/CMakeLists.txt @@ -7,11 +7,13 @@ set( ilc_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ilcflags.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/obe.cpp ) set( ilc_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/ilcflags.h + ${CMAKE_CURRENT_SOURCE_DIR}/obe.h ) # Define the target with appropriate dependencies diff --git a/modules/ilc/obe.cpp b/modules/ilc/obe.cpp new file mode 100644 index 00000000..b834cc17 --- /dev/null +++ b/modules/ilc/obe.cpp @@ -0,0 +1,810 @@ +/** + * This file is a part of channelflow version 2.0. + * License is GNU GPL version 2 or later: https://channelflow.org/license + * + * Original author: Florian Reetz + */ + +#include "modules/ilc/obe.h" + +namespace chflow { + +void momentumNL(const FlowField& u, const FlowField& T, ChebyCoeff Ubase, ChebyCoeff Wbase, FlowField& f, + FlowField& tmp, ILCFlags flags) { + // compute the nonlinear term of NSE in the usual Channelflow style + navierstokesNL(u, Ubase, Wbase, f, tmp, flags); + + // substract the linear temperature coupling term + // f -= sin(gamma)*T e_x + cos(gamma)*T e_y + Real gammax = flags.gammax; + Real grav = flags.grav; +#ifdef HAVE_MPI + for (int mz = f.mzlocmin(); mz < f.mzlocmin() + f.Mzloc(); mz++) + for (int mx = f.mxlocmin(); mx < f.mxlocmin() + f.Mxloc(); mx++) + for (int ny = 0; ny < f.Ny(); ny++) { + f.cmplx(mx, ny, mz, 0) -= grav * sin(gammax) * T.cmplx(mx, ny, mz, 0); + f.cmplx(mx, ny, mz, 1) -= grav * cos(gammax) * T.cmplx(mx, ny, mz, 0); + } +#else + for (int ny = 0; ny < f.Ny(); ny++) + for (int mx = f.mxlocmin(); mx < f.mxlocmin() + f.Mxloc(); mx++) + for (int mz = f.mzlocmin(); mz < f.mzlocmin() + f.Mzloc(); mz++) { + f.cmplx(mx, ny, mz, 0) -= grav * sin(gammax) * T.cmplx(mx, ny, mz, 0); + f.cmplx(mx, ny, mz, 1) -= grav * cos(gammax) * T.cmplx(mx, ny, mz, 0); + } +#endif + + // dealiasing modes + if (flags.dealias_xz()) + f.zeroPaddedModes(); +} + +void temperatureNL(const FlowField& u_, const FlowField& T_, ChebyCoeff Ubase, ChebyCoeff Wbase, ChebyCoeff Tbase, + FlowField& f, FlowField& tmp, ILCFlags flags) { + FlowField& u = const_cast(u_); + FlowField& T = const_cast(T_); + + // f += Base; + for (int ny = 0; ny < u.Ny(); ++ny) { + if (u.taskid() == u.task_coeff(0, 0)) { + u.cmplx(0, ny, 0, 0) += Complex(Ubase(ny), 0.0); + u.cmplx(0, ny, 0, 2) += Complex(Wbase(ny), 0.0); + } + if (u.taskid() == u.task_coeff(0, 0)) + T.cmplx(0, ny, 0, 0) += Complex(Tbase(ny), 0.0); + } + if (u.taskid() == u.task_coeff(0, 0)) { + u.cmplx(0, 0, 0, 1) -= Complex(flags.Vsuck, 0.); + } + + // compute the nonlinearity (temperature advection (u*grad)T ) analogous to the convectiveNL and store in f + dotgradScalar(u, T, f, tmp); + + // f -= Base; + for (int ny = 0; ny < u.Ny(); ++ny) { + if (u.taskid() == u.task_coeff(0, 0)) { + u.cmplx(0, ny, 0, 0) -= Complex(Ubase(ny), 0.0); + u.cmplx(0, ny, 0, 2) -= Complex(Wbase(ny), 0.0); + } + if (u.taskid() == u.task_coeff(0, 0)) + T.cmplx(0, ny, 0, 0) -= Complex(Tbase(ny), 0.0); + } + if (u.taskid() == u.task_coeff(0, 0)) { + u.cmplx(0, 0, 0, 1) += Complex(flags.Vsuck, 0.); + } + + // dealiasing modes + if (flags.dealias_xz()) + f.zeroPaddedModes(); +} + +OBE::OBE(const std::vector& fields, const ILCFlags& flags) + : NSE(fields, flags), + heatsolver_(0), // heatsolvers are allocated when reset_lambda is called for the first time + flags_(flags), + gsingx_(0), + gcosgx_(0), + Tref_(flags.t_ref), + Tbase_(), + Tbaseyy_(), + Pbasey_(), + Pbasex_(0), + Cu_(), + Cw_(), + Ct_(), + nonzCu_(false), + nonzCw_(false), + nonzCt_(false), + Tk_(Nyd_, a_, b_, Spectral), + Rtk_(Nyd_, a_, b_, Spectral), + baseflow_(false), + constraint_(false) { + gsingx_ = flags.grav * sin(flags_.gammax); + gcosgx_ = flags.grav * cos(flags_.gammax); + + // set member variables for base flow + createILCBaseFlow(); + Pbasex_ = hydrostaticPressureGradientX(flags_); + Pbasey_ = hydrostaticPressureGradientY(Tbase_, flags_); + + // set member variables for contraint + initILCConstraint(fields[0]); + + // define the constant terms of the OBE + createConstants(); +} + +OBE::OBE(const std::vector& fields, const std::vector& base, const ILCFlags& flags) + : NSE(fields, base, flags), + heatsolver_(0), // heatsolvers are allocated when reset_lambda is called for the first time + flags_(flags), + gsingx_(0), + gcosgx_(0), + Tref_(flags.t_ref), + Tbase_(base[2]), + Tbaseyy_(), + Pbasey_(), + Pbasex_(0), + Cu_(), + Cw_(), + Ct_(), + nonzCu_(false), + nonzCw_(false), + nonzCt_(false), + Tk_(Nyd_, a_, b_, Spectral), + Rtk_(Nyd_, a_, b_, Spectral), + baseflow_(false), + constraint_(false) { + gsingx_ = flags.grav * sin(flags_.gammax); + gcosgx_ = flags.grav * cos(flags_.gammax); + + baseflow_ = true; // base flow is passed to constructor + Pbasex_ = hydrostaticPressureGradientX(flags_); + Pbasey_ = hydrostaticPressureGradientY(Tbase_, flags_); + + // set member variables for contraint + initILCConstraint(fields[0]); + + // define the constant terms of the OBE + createConstants(); +} + +OBE::~OBE() { + if (tausolver_) { + for (uint j = 0; j < lambda_t_.size(); ++j) { + for (int mx = 0; mx < Mxloc_; ++mx) { + delete[] tausolver_[j][mx]; // undo new #3 + tausolver_[j][mx] = 0; + } + delete[] tausolver_[j]; // undo new #2 + tausolver_[j] = 0; + } + delete[] tausolver_; // undo new #1 + tausolver_ = 0; + } + + if (heatsolver_) { + for (uint j = 0; j < lambda_t_.size(); ++j) { + for (int mx = 0; mx < Mxloc_; ++mx) { + delete[] heatsolver_[j][mx]; // undo new #3 + heatsolver_[j][mx] = 0; + } + delete[] heatsolver_[j]; // undo new #2 + heatsolver_[j] = 0; + } + delete[] heatsolver_; // undo new #1 + heatsolver_ = 0; + } +} + +void OBE::nonlinear(const std::vector& infields, std::vector& outfields) { + // The first entry in vector must be velocity FlowField, the second a temperature FlowField. + // Pressure as third entry in in/outfields is not touched. + momentumNL(infields[0], infields[1], Ubase_, Wbase_, outfields[0], tmp_, flags_); + temperatureNL(infields[0], infields[1], Ubase_, Wbase_, Tbase_, outfields[1], tmp_, flags_); +} + +void OBE::linear(const std::vector& infields, std::vector& outfields) { + // Method takes input fields {u,press} and computes the linear terms for velocity output field {u} + + // Make sure user does not expect a pressure output. Outfields should be created outside OBE with OBE::createRHS() + assert(infields.size() == (outfields.size() + 1)); + const int kxmax = infields[0].kxmax(); + const int kzmax = infields[0].kzmax(); + const Real nu = flags_.nu; + const Real kappa = flags_.kappa; + + // Loop over Fourier modes. 2nd derivative and summation of linear term + // is most sufficient on ComplexChebyCoeff. Therefore, the old loop structure is kept. + for (lint mx = mxlocmin_; mx < mxlocmin_ + Mxloc_; ++mx) { + const int kx = infields[0].kx(mx); + for (lint mz = mzlocmin_; mz < mzlocmin_ + Mzloc_; ++mz) { + const int kz = infields[0].kz(mz); + + // Skip last and aliased modes + if ((kx == kxmax || kz == kzmax) || (flags_.dealias_xz() && isAliasedMode(kx, kz))) + break; + + // Compute linear temperature terms + //================================ + // Goal is to compute + // L = nu uj" - kappa2 nu uj - grad qj + C + + // Extract relevant Fourier modes of uj and qj + for (int ny = 0; ny < Nyd_; ++ny) { + uk_.set(ny, nu * infields[0].cmplx(mx, ny, mz, 0)); + vk_.set(ny, nu * infields[0].cmplx(mx, ny, mz, 1)); + wk_.set(ny, nu * infields[0].cmplx(mx, ny, mz, 2)); + Pk_.set(ny, infields[2].cmplx(mx, ny, mz, 0)); // pressure is 3. entry in fields + } + + // (1) Put nu uj" into in R. (Pyk_ is used as tmp workspace) + diff2(uk_, Ruk_, Pyk_); + diff2(vk_, Rvk_, Pyk_); + diff2(wk_, Rwk_, Pyk_); + + // (2) Put qn' into Pyk (compute y-comp of pressure gradient). + diff(Pk_, Pyk_); + + // (3) Summation of all derivative terms and assignment to output velocity field. + const Real k2 = 4 * pi * pi * (square(kx / Lx_) + square(kz / Lz_)); + const Complex Dx = infields[0].Dx(mx); + const Complex Dz = infields[0].Dz(mz); + for (int ny = 0; ny < Nyd_; ++ny) { + outfields[0].cmplx(mx, ny, mz, 0) = Ruk_[ny] - k2 * uk_[ny] - Dx * Pk_[ny]; + outfields[0].cmplx(mx, ny, mz, 1) = Rvk_[ny] - k2 * vk_[ny] - Pyk_[ny]; + outfields[0].cmplx(mx, ny, mz, 2) = Rwk_[ny] - k2 * wk_[ny] - Dz * Pk_[ny]; + } + + // (4) Add const. terms + if (kx == 0 && kz == 0) { + // L includes const dissipation term of Ubase and Wbase: nu Uyy, nu Wyy + // and bouyancy term of Temperature profile: sin(gamma)(Tbase_-Tref) + // absolute bouyancy term cancels with Pbasex_ (both constant) + if (nonzCu_ || nonzCw_) { + for (int ny = 0; ny < My_; ++ny) { + outfields[0].cmplx(mx, ny, mz, 0) += Cu_[ny]; + outfields[0].cmplx(mx, ny, mz, 2) += Cw_[ny]; + } + } + // Add base pressure gradient depending on the constraint + if (flags_.constraint == PressureGradient) { + // dPdx is supplied as dPdxRef + outfields[0].cmplx(mx, 0, mz, 0) -= Complex(dPdxRef_, 0); + outfields[0].cmplx(mx, 0, mz, 2) -= Complex(dPdzRef_, 0); + } else { // const bulk velocity + // actual dPdx is unknown but defined by constraint of bulk velocity + // Determine actual dPdx from Ubase + u. + Real Ly = b_ - a_; + diff(uk_, Ruk_); + diff(wk_, Rwk_); + Real dPdxAct = Re(Ruk_.eval_b() - Ruk_.eval_a()) / Ly; + Real dPdzAct = Re(Rwk_.eval_b() - Rwk_.eval_a()) / Ly; + ChebyCoeff Ubasey = diff(Ubase_); + ChebyCoeff Wbasey = diff(Wbase_); + if (Ubase_.length() != 0) + dPdxAct += nu * (Ubasey.eval_b() - Ubasey.eval_a()) / Ly; + if (Wbase_.length() != 0) + dPdzAct += nu * (Wbasey.eval_b() - Wbasey.eval_a()) / Ly; + // add press. gradient to linear term + outfields[0].cmplx(mx, 0, mz, 0) -= Complex(dPdxAct, 0); + outfields[0].cmplx(mx, 0, mz, 2) -= Complex(dPdzAct, 0); + } + } // End of const. terms + + // Compute linear heat equation terms + //================================ + // Goal is to compute + // L = kappa T" - k2 kappa T - Ubase dT/dx + C + + // Extract relevant Fourier modes of T: use Pk and Rxk + for (int ny = 0; ny < Nyd_; ++ny) + Tk_.set(ny, kappa * infields[1].cmplx(mx, ny, mz, 0)); + + // (1) Put kappa T" into in R. (Pyk_ is used as tmp workspace) + diff2(Tk_, Rtk_, Pyk_); + + // (2+3) Summation of both diffusive terms and linear advection of temperature. + // k2 and Dx were defined above for the current Fourier mode + for (int ny = 0; ny < Nyd_; ++ny) + // from nonlinear: - Dx*Tk_[ny]*Complex(Ubase_[ny],0)/kappa; + outfields[1].cmplx(mx, ny, mz, 0) = Rtk_[ny] - k2 * Tk_[ny]; + + // (4) Add const. terms + if (kx == 0 && kz == 0) { + // L includes const diffusion term of Tbase: kappa Tbase_yy + if (nonzCt_) + for (int ny = 0; ny < My_; ++ny) + outfields[1].cmplx(mx, ny, mz, 0) += Ct_[ny]; + } + } + } // End of loop over Fourier modes +} + +void OBE::solve(std::vector& outfields, const std::vector& rhs, const int s) { + // Method takes a right hand side {u} and solves for output fields {u,press} + + // Make sure user provides correct RHS which can be created outside NSE with NSE::createRHS() + assert(outfields.size() == (rhs.size() + 1)); + const int kxmax = outfields[0].kxmax(); + const int kzmax = outfields[0].kzmax(); + + // Update each Fourier mode with solution of the implicit problem + for (lint ix = 0; ix < Mxloc_; ++ix) { + const lint mx = ix + mxlocmin_; + const int kx = outfields[0].kx(mx); + + for (lint iz = 0; iz < Mzloc_; ++iz) { + const lint mz = iz + mzlocmin_; + const int kz = outfields[0].kz(mz); + + // Skip last and aliased modes + if ((kx == kxmax || kz == kzmax) || (flags_.dealias_xz() && isAliasedMode(kx, kz))) + break; + + // Construct ComplexChebyCoeff + for (int ny = 0; ny < Nyd_; ++ny) { + Ruk_.set(ny, rhs[0].cmplx(mx, ny, mz, 0)); + Rvk_.set(ny, rhs[0].cmplx(mx, ny, mz, 1)); + Rwk_.set(ny, rhs[0].cmplx(mx, ny, mz, 2)); + // negative RHS because HelmholtzSolver solves the negative problem + Rtk_.set(ny, -rhs[1].cmplx(mx, ny, mz, 0)); + } + + // Solve the tau equations for momentum + //============================= + if (kx != 0 || kz != 0) + tausolver_[s][ix][iz].solve(uk_, vk_, wk_, Pk_, Ruk_, Rvk_, Rwk_); + // solve(ix,iz,uk_,vk_,wk_,Pk_, Ruk_,Rvk_,Rwk_); + else { // kx,kz == 0,0 + // LHS includes also the constant terms C which can be added to RHS + if (nonzCu_ || nonzCw_) { + for (int ny = 0; ny < My_; ++ny) { + Ruk_.re[ny] += Cu_.re[ny]; + Rwk_.re[ny] += Cw_.re[ny]; + } + } + + if (flags_.constraint == PressureGradient) { + // pressure is supplied, put on RHS of tau eqn + Ruk_.re[0] -= dPdxRef_; + Rwk_.re[0] -= dPdzRef_; + tausolver_[s][ix][iz].solve(uk_, vk_, wk_, Pk_, Ruk_, Rvk_, Rwk_); + // solve(ix,iz,uk_, vk_, wk_, Pk_, Ruk_,Rvk_,Rwk_); + // Bulk vel is free variable determined from soln of tau eqn //TODO: write method that computes + // UbulkAct everytime it is needed + + } else { // const bulk velocity + // bulk velocity is supplied, use alternative tau solver + + // Use tausolver with additional variable and constraint: + // free variable: dPdxAct at next time-step, + // constraint: UbulkBase + mean(u) = UbulkRef. + tausolver_[s][ix][iz].solve(uk_, vk_, wk_, Pk_, dPdxAct_, dPdzAct_, Ruk_, Rvk_, Rwk_, + UbulkRef_ - UbulkBase_, WbulkRef_ - WbulkBase_); + // solve(ix,iz,uk_, vk_, wk_, Pk_, dPdxAct_, dPdzAct_, + // Ruk_, Rvk_, Rwk_, + // UbulkRef_ - UbulkBase_, + // WbulkRef_ - WbulkBase_); + + // test if UbulkRef == UbulkAct = UbulkBase_ + uk_.re.mean() + assert((UbulkRef_ - UbulkBase_ - uk_.re.mean()) < 1e-15); + // test if WbulkRef == WbulkAct = WbulkBase_ + wk_.re.mean() + assert((WbulkRef_ - WbulkBase_ - wk_.re.mean()) < 1e-15); + } + } + // Load solutions into u and p. + // Because of FFTW complex symmetries + // The 0,0 mode must be real. + // For Nx even, the kxmax,0 mode must be real + // For Nz even, the 0,kzmax mode must be real + // For Nx,Nz even, the kxmax,kzmax mode must be real + if ((kx == 0 && kz == 0) || (outfields[0].Nx() % 2 == 0 && kx == kxmax && kz == 0) || + (outfields[0].Nz() % 2 == 0 && kz == kzmax && kx == 0) || + (outfields[0].Nx() % 2 == 0 && outfields[0].Nz() % 2 == 0 && kx == kxmax && kz == kzmax)) { + for (int ny = 0; ny < Nyd_; ++ny) { + outfields[0].cmplx(mx, ny, mz, 0) = Complex(Re(uk_[ny]), 0.0); + outfields[0].cmplx(mx, ny, mz, 1) = Complex(Re(vk_[ny]), 0.0); + outfields[0].cmplx(mx, ny, mz, 2) = Complex(Re(wk_[ny]), 0.0); + outfields[2].cmplx(mx, ny, mz, 0) = Complex(Re(Pk_[ny]), 0.0); + } + } + // The normal case, for general kx,kz + else + for (int ny = 0; ny < Nyd_; ++ny) { + outfields[0].cmplx(mx, ny, mz, 0) = uk_[ny]; + outfields[0].cmplx(mx, ny, mz, 1) = vk_[ny]; + outfields[0].cmplx(mx, ny, mz, 2) = wk_[ny]; + outfields[2].cmplx(mx, ny, mz, 0) = Pk_[ny]; + } + + // Solve the helmholtz problem for the heat equation + //============================= + if (kx != 0 || kz != 0) { + heatsolver_[s][ix][iz].solve(Tk_.re, Rtk_.re, 0.0, 0.0); // BC are considered through the base profile + heatsolver_[s][ix][iz].solve(Tk_.im, Rtk_.im, 0.0, 0.0); + } else { // kx,kz == 0,0 + // LHS includes also the constant term C=kappa Tbase_yy, which can be added to RHS + if (nonzCt_) { + for (int ny = 0; ny < My_; ++ny) + Rtk_.re[ny] -= Ct_.re[ny]; + } + + heatsolver_[s][ix][iz].solve(Tk_.re, Rtk_.re, 0.0, 0.0); + heatsolver_[s][ix][iz].solve(Tk_.im, Rtk_.im, 0.0, 0.0); + } + + // Load solution into T. + // Because of FFTW complex symmetries + // The 0,0 mode must be real. + // For Nx even, the kxmax,0 mode must be real + // For Nz even, the 0,kzmax mode must be real + // For Nx,Nz even, the kxmax,kzmax mode must be real + if ((kx == 0 && kz == 0) || (outfields[1].Nx() % 2 == 0 && kx == kxmax && kz == 0) || + (outfields[1].Nz() % 2 == 0 && kz == kzmax && kx == 0) || + (outfields[1].Nx() % 2 == 0 && outfields[1].Nz() % 2 == 0 && kx == kxmax && kz == kzmax)) { + for (int ny = 0; ny < Nyd_; ++ny) + outfields[1].cmplx(mx, ny, mz, 0) = Complex(Re(Tk_[ny]), 0.0); + + } + // The normal case, for general kx,kz + else + for (int ny = 0; ny < Nyd_; ++ny) + outfields[1].cmplx(mx, ny, mz, 0) = Tk_[ny]; + + } // End of loop over Fourier modes + } +} + +void OBE::reset_lambda(std::vector lambda_t) { + lambda_t_ = lambda_t; + + if ((tausolver_ == 0) && (heatsolver_ == 0)) { // TauSolver need to be constructed + // Allocate memory for [Nsubsteps x Mx_ x Mz_] Tausolver cfarray + tausolver_ = new TauSolver**[lambda_t.size()]; // new #1 + heatsolver_ = new HelmholtzSolver**[lambda_t.size()]; + for (uint j = 0; j < lambda_t.size(); ++j) { + tausolver_[j] = new TauSolver*[Mxloc_]; // new #2 + heatsolver_[j] = new HelmholtzSolver*[Mxloc_]; + for (int mx = 0; mx < Mxloc_; ++mx) { + tausolver_[j][mx] = new TauSolver[Mzloc_]; // new #3 + heatsolver_[j][mx] = new HelmholtzSolver[Mzloc_]; + } + } + } else if ((tausolver_ == 0) && (heatsolver_ != 0)) { + tausolver_ = new TauSolver**[lambda_t.size()]; // new #1 + for (uint j = 0; j < lambda_t.size(); ++j) { + tausolver_[j] = new TauSolver*[Mxloc_]; // new #2 + for (int mx = 0; mx < Mxloc_; ++mx) + tausolver_[j][mx] = new TauSolver[Mzloc_]; // new #3 + } + } else if ((tausolver_ != 0) && (heatsolver_ == 0)) { + heatsolver_ = new HelmholtzSolver**[lambda_t.size()]; // new #1 + for (uint j = 0; j < lambda_t.size(); ++j) { + heatsolver_[j] = new HelmholtzSolver*[Mxloc_]; // new #2 + for (int mx = 0; mx < Mxloc_; ++mx) + heatsolver_[j][mx] = new HelmholtzSolver[Mzloc_]; // new #3 + } + } + + // Configure tausolvers + // FlowField u=fields_[0]; + const Real nu = flags_.nu; + const Real kappa = flags_.kappa; + const Real c = 4.0 * square(pi); + // const int kxmax = u.kxmax(); + // const int kzmax = u.kzmax(); + for (uint j = 0; j < lambda_t.size(); ++j) { + for (int mx = 0; mx < Mxloc_; ++mx) { + int kx = kxloc_[mx]; + for (int mz = 0; mz < Mzloc_; ++mz) { + int kz = kzloc_[mz]; + Real lambda_tau = lambda_t[j] + nu * c * (square(kx / Lx_) + square(kz / Lz_)); + Real lambda_heat = lambda_t[j] + kappa * c * (square(kx / Lx_) + square(kz / Lz_)); + + if ((kx != kxmax_ || kz != kzmax_) && (!flags_.dealias_xz() || !isAliasedMode(kx, kz))) { + tausolver_[j][mx][mz] = + TauSolver(kx, kz, Lx_, Lz_, a_, b_, lambda_tau, nu, Nyd_, flags_.taucorrection); + heatsolver_[j][mx][mz] = HelmholtzSolver(Nyd_, a_, b_, lambda_heat, kappa); + } + } + } + } +} + +const ChebyCoeff& OBE::Tbase() const { return Tbase_; } +const ChebyCoeff& OBE::Ubase() const { return Ubase_; } +const ChebyCoeff& OBE::Wbase() const { return Wbase_; } + +std::vector OBE::createRHS(const std::vector& fields) const { + return {fields[0], fields[1]}; // return only velocity and temperature fields +} + +std::vector> OBE::createSymmVec() const { + // velocity symmetry + cfarray usym = SymmetryList(1); // unity operation + if (flags_.symmetries.length() > 0) + usym = flags_.symmetries; + // temperature symmetry + cfarray tsym = SymmetryList(1); // unity operation + if (flags_.tempsymmetries.length() > 0) + tsym = flags_.tempsymmetries; + // pressure symmetry + cfarray psym = SymmetryList(1); // unity operation + return {usym, tsym, psym}; +} + +void OBE::createILCBaseFlow() { + Real ulowerwall = flags_.ulowerwall; + Real uupperwall = flags_.uupperwall; + Real wlowerwall = flags_.wlowerwall; + Real wupperwall = flags_.wupperwall; + + switch (flags_.baseflow) { + case ZeroBase: + Ubase_ = ChebyCoeff(My_, a_, b_, Spectral); + Wbase_ = ChebyCoeff(My_, a_, b_, Spectral); + Tbase_ = ChebyCoeff(My_, a_, b_, Spectral); + break; + case LinearBase: + std::cerr << "error in OBE::createBaseFlow :\n"; + std::cerr << "LinearBase is not defined in ILC.\n"; + break; + case ParabolicBase: + std::cerr << "error in OBE::createBaseFlow :\n"; + std::cerr << "ParabolicBase is not defined in ILC.\n"; + break; + case SuctionBase: + std::cerr << "error in OBE::createBaseFlow :\n"; + std::cerr << "ParabolicBase is not defined in ILC.\n"; + break; + case LaminarBase: + Ubase_ = laminarVelocityProfile(flags_.gammax, flags_.dPdx, flags_.Ubulk, ulowerwall, uupperwall, a_, b_, + My_, flags_); + + // choosing angle zero removes buoyancy effect + Wbase_ = + laminarVelocityProfile(0.0, flags_.dPdz, flags_.Wbulk, wlowerwall, wupperwall, a_, b_, My_, flags_); + + Tbase_ = linearTemperatureProfile(a_, b_, My_, flags_); + + break; + case ArbitraryBase: + std::cerr << "error in NSE::createBaseFlow :\n"; + std::cerr << "flags.baseflow is ArbitraryBase.\n"; + std::cerr << "Please provide {Ubase, Wbase} when constructing DNS.\n"; + cferror(""); + default: + std::cerr << "error in NSE::createBaseFlow :\n"; + std::cerr << "flags.baseflow should be ZeroBase, LinearBase, ParabolicBase, LaminarBase, SuctionBase.\n"; + std::cerr << "Other cases require use of the DNS::DNS(fields, base, flags) constructor.\n"; + cferror(""); + } + + baseflow_ = true; +} + +void OBE::createConstants() { + if ((!baseflow_) || (!constraint_)) + std::cerr << "OBE::createConstants: Not all base flow members have been initialized." << std::endl; + + ComplexChebyCoeff c(My_, a_, b_, Spectral); + Real nu = flags_.nu; + Real kappa = flags_.kappa; + Real eta = 1.0 / (flags_.alpha * (flags_.tlowerwall - flags_.tupperwall)); + Real t_ref = flags_.t_ref; + + // constant u-term: + Real hydrostatx = gsingx_ * (Tbase_[0] - eta - t_ref) - Pbasex_; + if (abs(hydrostatx) > 1e-15) + *flags_.logstream << "ILC with hydrostatic dPdx." << std::endl; + if (Ubaseyy_.length() > 0) { + c[0] = Complex(nu * Ubaseyy_[0] + hydrostatx, 0); + if ((abs(c[0]) > 1e-15) && !nonzCu_) + nonzCu_ = true; + for (int ny = 1; ny < My_; ++ny) { + c[ny] = Complex(nu * Ubaseyy_[ny] + gsingx_ * Tbase_[ny], 0); + if ((abs(c[ny]) > 1e-15) && !nonzCu_) + nonzCu_ = true; + } + } + if (nonzCu_) { + Cu_ = c; + *flags_.logstream << "ILC with nonzero U-const." << std::endl; + } + + // check that constant v-term is zero: + Real hydrostaty = gcosgx_ * (Tbase_[0] - eta - t_ref) - Pbasey_[0]; + if (abs(hydrostaty) > 1e-15) + std::cerr << "Wall-normal hydrostatic pressure is unballanced" << std::endl; + for (int ny = 1; ny < My_; ++ny) + if (abs(gcosgx_ * Tbase_[ny] - Pbasey_[ny]) > 1e-15) + std::cerr << "Wall-normal hydrostatic pressure is unballanced" << std::endl; + + // constant w-term: + if (Wbaseyy_.length() > 0) { + for (int ny = 0; ny < My_; ++ny) { + c[ny] = Complex(nu * Wbaseyy_[ny], 0); + if ((abs(c[ny]) > 1e-15) && !nonzCw_) + nonzCw_ = true; + } + } + if (nonzCw_) { + Cw_ = c; + *flags_.logstream << "ILC with nonzero W-const." << std::endl; + } + + // constant t-term: + if (Tbaseyy_.length() > 0) { + for (int ny = 1; ny < My_; ++ny) { + c[ny] = Complex(kappa * Tbaseyy_[ny], 0); + if ((abs(c[ny]) > 1e-15) && !nonzCt_) + nonzCt_ = true; + } + } + if (nonzCt_) { + Ct_ = c; + *flags_.logstream << "ILC with nonzero T-const." << std::endl; + } +} + +void OBE::initILCConstraint(const FlowField& u) { + if (!baseflow_) + std::cerr << "OBE::initConstraint: Base flow has not been created." << std::endl; + + // Calculate Ubaseyy_ and related quantities + UbulkBase_ = Ubase_.mean(); + ChebyCoeff Ubasey = diff(Ubase_); + Ubaseyy_ = diff(Ubasey); + WbulkBase_ = Wbase_.mean(); + ChebyCoeff Wbasey = diff(Wbase_); + Wbaseyy_ = diff(Wbasey); + + ChebyCoeff Tbasey = diff(Tbase_); + Tbaseyy_ = diff(Tbasey); + + // Determine actual Ubulk and dPdx from initial data Ubase + u. + Real Ly = b_ - a_; + ChebyCoeff u00(My_, a_, b_, Spectral); + Real reucmplx = 0; + for (int ny = 0; ny < My_; ++ny) { + if (u.taskid() == 0) + reucmplx = Re(u.cmplx(0, ny, 0, 0)); +#ifdef HAVE_MPI + MPI_Bcast(&reucmplx, 1, MPI_DOUBLE, u.task_coeff(0, 0), *u.comm_world()); +#endif + u00[ny] = reucmplx; + } + ChebyCoeff du00dy = diff(u00); + UbulkAct_ = UbulkBase_ + u00.mean(); + dPdxAct_ = flags_.nu * (du00dy.eval_b() - du00dy.eval_a()) / Ly; + ChebyCoeff w00(My_, a_, b_, Spectral); + for (int ny = 0; ny < My_; ++ny) { + if (u.taskid() == 0) + reucmplx = Re(u.cmplx(0, ny, 0, 2)); +#ifdef HAVE_MPI + MPI_Bcast(&reucmplx, 1, MPI_DOUBLE, u.task_coeff(0, 0), *u.comm_world()); +#endif + w00[ny] = reucmplx; + } + ChebyCoeff dw00dy = diff(w00); + WbulkAct_ = WbulkBase_ + w00.mean(); + dPdzAct_ = flags_.nu * (dw00dy.eval_b() - dw00dy.eval_a()) / Ly; + +#ifdef HAVE_MPI + MPI_Bcast(&dPdxAct_, 1, MPI_DOUBLE, u.task_coeff(0, 0), *u.comm_world()); + MPI_Bcast(&UbulkAct_, 1, MPI_DOUBLE, u.task_coeff(0, 0), *u.comm_world()); + MPI_Bcast(&dPdzAct_, 1, MPI_DOUBLE, u.task_coeff(0, 0), *u.comm_world()); + MPI_Bcast(&WbulkAct_, 1, MPI_DOUBLE, u.task_coeff(0, 0), *u.comm_world()); +#endif + + if (Ubase_.length() != 0) + dPdxAct_ += flags_.nu * (Ubasey.eval_b() - Ubasey.eval_a()) / Ly; + if (Wbase_.length() != 0) + dPdzAct_ += flags_.nu * (Wbasey.eval_b() - Wbasey.eval_a()) / Ly; + + if (flags_.constraint == BulkVelocity) { + UbulkRef_ = flags_.Ubulk; + WbulkRef_ = flags_.Wbulk; + } else { + dPdxAct_ = flags_.dPdx; + dPdxRef_ = flags_.dPdx; + dPdzAct_ = flags_.dPdz; + dPdzRef_ = flags_.dPdz; + } + + constraint_ = true; +} + +ChebyCoeff laminarVelocityProfile(Real gamma, Real dPdx, Real Ubulk, Real Ua, Real Ub, Real a, Real b, int Ny, + ILCFlags flags) { + MeanConstraint constraint = flags.constraint; + Real nu = flags.nu; + Real Vsuck = flags.Vsuck; + Real dT = flags.tlowerwall - flags.tupperwall; + Real mT = 0.5 * (flags.tlowerwall + flags.tupperwall); + Real Tref = flags.t_ref; + + ChebyCoeff u(Ny, a, b, Spectral); + Real grav = flags.grav; + Real q = grav * sin(gamma) / nu / 16.0; + + if (constraint == BulkVelocity) { + cferror("Using ILC with constraint BulkVelocity is not implemented yet"); + } else { + if (Vsuck < 1e-14) { + if (dPdx < 1e-14) { + u[0] = q * (mT - Tref) + 0.5 * (Ub + Ua); // See documentation about base solution + u[1] = -q / 12.0 * dT + 0.5 * (Ub - Ua); + u[2] = -q * (mT - Tref); + u[3] = q / 12.0 * dT; + } else { + cferror("Using ILC with nonzero dPdx is not implemented yet"); + } + } else { + cferror("Using ILC with SuctionVelocity is not implemented yet"); + } + } + return u; +} + +ChebyCoeff linearTemperatureProfile(Real a, Real b, int Ny, ILCFlags flags) { + MeanConstraint constraint = flags.constraint; + + ChebyCoeff T(Ny, a, b, Spectral); + Real Vsuck = flags.Vsuck; + Real dPdx = flags.dPdx; + Real Ta = flags.tlowerwall; + Real Tb = flags.tupperwall; + + if (constraint == BulkVelocity) { + cferror("Using ILC with constraint BulkVelocity is not implemented yet"); + } else { + if (Vsuck < 1e-14) { + if (dPdx < 1e-14) { + T[0] = 0.5 * (Tb + Ta); // the boundary conditions are given in units of Delta_T=Ta-Tb + T[1] = 0.5 * (Tb - Ta); + } else { + cferror("Using ILC with nonzero dPdx is not implemented yet"); + } + } else { + cferror("Using ILC with SuctionVelocity is not implemented yet"); + } + } + return T; +} + +Real hydrostaticPressureGradientX(ILCFlags flags) { + MeanConstraint constraint = flags.constraint; + + Real Px = 0.0; + Real Vsuck = flags.Vsuck; + Real dPdx = flags.dPdx; + Real sgamma = sin(flags.gammax); + Real eta = 1.0 / (flags.alpha * (flags.tlowerwall - flags.tupperwall)); + Real grav = flags.grav; + + if (constraint == BulkVelocity) { + cferror("Using ILC with constraint BulkVelocity is not implemented yet"); + } else { + if (Vsuck < 1e-14) { + if (dPdx < 1e-14) { + Px = -grav * eta * sgamma; + } else { + cferror("Using ILC with nonzero dPdx is not implemented yet"); + } + } else { + cferror("Using ILC with SuctionVelocity is not implemented yet"); + } + } + return Px; +} + +ChebyCoeff hydrostaticPressureGradientY(ChebyCoeff Tbase, ILCFlags flags) { + MeanConstraint constraint = flags.constraint; + + ChebyCoeff Py(Tbase); + Real Vsuck = flags.Vsuck; + Real dPdx = flags.dPdx; + Real cgamma = cos(flags.gammax); + Real eta = 1.0 / (flags.alpha * (flags.tlowerwall - flags.tupperwall)); + Real grav = flags.grav; + + if (constraint == BulkVelocity) { + cferror("Using ILC with constraint BulkVelocity is not implemented yet"); + } else { + if (Vsuck < 1e-14) { + if (dPdx < 1e-14) { + Py[0] = grav * cgamma * (Tbase[0] - eta - flags.t_ref); // TODO: implement general loop + Py[1] = grav * cgamma * Tbase[1]; + } else { + cferror("Using ILC with nonzero dPdx is not implemented yet"); + } + } else { + cferror("Using ILC with SuctionVelocity is not implemented yet"); + } + } + return Py; +} + +} // namespace channelflow \ No newline at end of file diff --git a/modules/ilc/obe.h b/modules/ilc/obe.h new file mode 100644 index 00000000..2599296d --- /dev/null +++ b/modules/ilc/obe.h @@ -0,0 +1,105 @@ +/** + * System class of Oberbeck-Boussinesq equations for standard channel flows + * + * This file is a part of channelflow version 2.0. + * License is GNU GPL version 2 or later: https://channelflow.org/license + * + * Original author: Florian Reetz + */ + +#ifndef OBE_H +#define OBE_H + +#include "channelflow/diffops.h" +#include "channelflow/flowfield.h" +#include "channelflow/nse.h" +#include "channelflow/tausolver.h" +#include "modules/ilc/ilcflags.h" + +namespace chflow { + +// nonlinear term of NSE plus the linear coupling term to the temperature equation +void momentumNL(const FlowField& u, const FlowField& T, ChebyCoeff Ubase, ChebyCoeff Wbase, FlowField& f, + FlowField& tmp, ILCFlags flags); + +// nonlinear term of heat equation plus the linear coupling term to the momentum equation +void temperatureNL(const FlowField& u, const FlowField& T, ChebyCoeff Ubase, ChebyCoeff Wbase, ChebyCoeff Tbase, + FlowField& f, FlowField& tmp, ILCFlags flags); + +class OBE : public NSE { + public: + // OBE (); + // OBE (const NSE& nse); + OBE(const std::vector& fields, const ILCFlags& flags); + OBE(const std::vector& fields, const std::vector& base, const ILCFlags& flags); + virtual ~OBE(); + + void nonlinear(const std::vector& infields, std::vector& outfields) override; + void linear(const std::vector& infields, std::vector& outfields) override; + + // calls a tausolver for each Fourier mode + void solve(std::vector& outfields, const std::vector& infields, const int i = 0) override; + + // redefines the tausolver objects with new time-stepping constant (allocates memory for tausolver at first use) + void reset_lambda(const std::vector lambda_t) override; + + // vector of RHS is smaller than of fields because of missing pressure equation + std::vector createRHS(const std::vector& fields) const override; + + // returns vector of symmetries confining the vector of fields to a subspace + std::vector> createSymmVec() const override; + + const ChebyCoeff& Tbase() const; + const ChebyCoeff& Ubase() const override; + const ChebyCoeff& Wbase() const override; + + protected: + HelmholtzSolver*** heatsolver_; // 3d cfarray of tausolvers, indexed by [i][mx][mz] for substep, Fourier Mode x,z + + ILCFlags flags_; // User-defined integration parameters + Real gsingx_; + Real gcosgx_; + Real Tref_; + + // additional base solution profiles + ChebyCoeff Tbase_; // temperature base profile (physical) + ChebyCoeff Tbaseyy_; // 2. deriv. of temperature base profile + ChebyCoeff Pbasey_; // wall normal pressure gradient (y-dependent) + Real Pbasex_; // along wall pressure gradient (const) + + // constant terms + ComplexChebyCoeff Cu_; // constant + ComplexChebyCoeff Cw_; + ComplexChebyCoeff Ct_; + bool nonzCu_; + bool nonzCw_; + bool nonzCt_; + + ComplexChebyCoeff Tk_; + ComplexChebyCoeff Rtk_; + + private: + void createILCBaseFlow(); + void initILCConstraint(const FlowField& u); // method called only at construction + void createConstants(); + + bool baseflow_; + bool constraint_; +}; + +// Construct laminar flow profile for given flow parameters. +// [a,b] == y position of [lower, upper] walls +// [ua,ub] == in-plane speed of [lower, upper] walls +// constraint == is mean pressure gradient fixed, or mean (bulk) velocity? +// dPdx, Ubulk == value of fixed pressure gradient or fixed Ubulk velocity +// Vsuck == suction velocity at walls (asymptotic suction boundary layer) +ChebyCoeff laminarVelocityProfile(Real gamma, Real dPdx, Real Ubulk, Real ua, Real ub, Real a, Real b, int Ny, + ILCFlags flags); + +ChebyCoeff linearTemperatureProfile(Real a, Real b, int Ny, ILCFlags flags); + +Real hydrostaticPressureGradientX(ILCFlags flags); +ChebyCoeff hydrostaticPressureGradientY(ChebyCoeff Tbase, ILCFlags flags); + +} // namespace channelflow +#endif \ No newline at end of file From 70742362d0435cfd15f8c7fb6be30ffd96d027cb Mon Sep 17 00:00:00 2001 From: Florian Reetz Date: Thu, 7 May 2020 13:37:30 +0200 Subject: [PATCH 03/10] Added the ilc class for handling DNS. --- modules/ilc/CMakeLists.txt | 2 + modules/ilc/ilc.cpp | 297 +++++++++++++++++++++++++++++++++++++ modules/ilc/ilc.h | 81 ++++++++++ modules/ilc/obe.cpp | 4 +- modules/ilc/obe.h | 14 +- 5 files changed, 389 insertions(+), 9 deletions(-) create mode 100644 modules/ilc/ilc.cpp create mode 100644 modules/ilc/ilc.h diff --git a/modules/ilc/CMakeLists.txt b/modules/ilc/CMakeLists.txt index 2fb94b11..eb750773 100644 --- a/modules/ilc/CMakeLists.txt +++ b/modules/ilc/CMakeLists.txt @@ -8,12 +8,14 @@ set( ilc_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ilcflags.cpp ${CMAKE_CURRENT_SOURCE_DIR}/obe.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ilc.cpp ) set( ilc_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/ilcflags.h ${CMAKE_CURRENT_SOURCE_DIR}/obe.h + ${CMAKE_CURRENT_SOURCE_DIR}/ilc.h ) # Define the target with appropriate dependencies diff --git a/modules/ilc/ilc.cpp b/modules/ilc/ilc.cpp new file mode 100644 index 00000000..50f61625 --- /dev/null +++ b/modules/ilc/ilc.cpp @@ -0,0 +1,297 @@ +/** + * This file is a part of channelflow version 2.0, https://channelflow.ch . + * License is GNU GPL version 2 or later: ./LICENSE + * + * Original author: Florian Reetz + */ + +#include "modules/ilc/ilc.h" + + +namespace chflow { + +int field2vector_size(const FlowField& u, const FlowField& temp) { + int Kx = u.kxmaxDealiased(); + int Kz = u.kzmaxDealiased(); + int Ny = u.Ny(); + // FIXME: Determine array size + // The original formula was + // int N = 4* ( Kx+2*Kx*Kz+Kz ) * ( Ny-3 ) +2* ( Ny-2 ); + // but I've not been able to twist my head enough to adapt it do distributed FlowFields. + // Since it doesn't take that much time, we now perform the loops twice, once empty to + // determine cfarray sizes and once with the actual data copying. // Tobias + int N = 0; + if (u.taskid() == u.task_coeff(0, 0)) + N += 2 * (Ny - 2); + for (int kx = 1; kx <= Kx; ++kx) + if (u.taskid() == u.task_coeff(u.mx(kx), 0)) + N += 2 * (Ny - 2) + 2 * (Ny - 4); + for (int kz = 1; kz <= Kz; ++kz) + if (u.taskid() == u.task_coeff(0, u.mz(kz))) + N += 2 * (Ny - 2) + 2 * (Ny - 4); + for (int kx = -Kx; kx <= Kx; ++kx) { + if (kx == 0) + continue; + int mx = u.mx(kx); + for (int kz = 1; kz <= Kz; ++kz) { + int mz = u.mz(kz); + if (u.taskid() == u.task_coeff(mx, mz)) { + N += 2 * (Ny - 2) + 2 * (Ny - 4); + } + } + } + + if (temp.taskid() == temp.task_coeff(0, 0)) + N += Ny; + for (int kx = 1; kx <= Kx; ++kx) + if (temp.taskid() == temp.task_coeff(temp.mx(kx), 0)) + N += 2 * Ny; + for (int kz = 1; kz <= Kz; ++kz) + if (temp.taskid() == temp.task_coeff(0, temp.mz(kz))) + N += 2 * Ny; + for (int kx = -Kx; kx <= Kx; kx++) { + if (kx == 0) + continue; + for (int kz = 1; kz <= Kz; kz++) { + if (temp.taskid() == temp.task_coeff(temp.mx(kx), temp.mz(kz))) { + N += 2 * Ny; + } + } + } + + return N; +} + +/** \brief Turn the two flowfields for velocity and temperature into one Eigen vector + * \param[in] u velocity field + * \param[in] temp temperature field + * \param[in] a vector for the linear algebra + * + * The vectorization of u is analog to the field2vector, temparture is piped entirely + * into the vector (a single independent dimensions) + */ +void field2vector(const FlowField& u, const FlowField& temp, Eigen::VectorXd& a) { + Eigen::VectorXd b; + assert(temp.xzstate() == Spectral && temp.ystate() == Spectral); + field2vector(u, b); + int Kx = temp.kxmaxDealiased(); + int Kz = temp.kzmaxDealiased(); + int Ny = temp.Ny(); + + int n = field2vector_size(u, temp); // b.size() +6*Kx*Kz*Ny; + + if (a.size() < n) + a.resize(n, true); + setToZero(a); + int pos = b.size(); + a.topRows(pos) = b; + + // (0,:,0) + for (int ny = 0; ny < Ny; ++ny) + if (temp.taskid() == temp.task_coeff(0, 0)) + a(pos++) = Re(temp.cmplx(0, ny, 0, 0)); + + for (int kx = 1; kx <= Kx; ++kx) { + int mx = temp.mx(kx); + if (temp.taskid() == temp.task_coeff(mx, 0)) { + for (int ny = 0; ny < Ny; ++ny) { + a(pos++) = Re(temp.cmplx(mx, ny, 0, 0)); + a(pos++) = Im(temp.cmplx(mx, ny, 0, 0)); + } + } + } + for (int kz = 1; kz <= Kz; ++kz) { + int mz = temp.mz(kz); + if (temp.taskid() == temp.task_coeff(0, mz)) { + for (int ny = 0; ny < Ny; ++ny) { + a(pos++) = Re(temp.cmplx(0, ny, mz, 0)); + a(pos++) = Im(temp.cmplx(0, ny, mz, 0)); + } + } + } + for (int kx = -Kx; kx <= Kx; kx++) { + if (kx == 0) + continue; + int mx = temp.mx(kx); + for (int kz = 1; kz <= Kz; kz++) { + int mz = temp.mz(kz); + if (u.taskid() == u.task_coeff(mx, mz)) { + for (int ny = 0; ny < Ny; ny++) { + a(pos++) = Re(temp.cmplx(mx, ny, mz, 0)); + a(pos++) = Im(temp.cmplx(mx, ny, mz, 0)); + } + } + } + } +} + +/** \brief Turn one Eigen vector into the two flowfields for velocity and temperature + * \param[in] a vector for the linear algebra + * \param[in] u velocity field + * \param[in] temp temperature field + * + * The vectorization of u is analog to the field2vector, temperature is piped entirely + * into the vector (a single independent dimension) + */ +void vector2field(const Eigen::VectorXd& a, FlowField& u, FlowField& temp) { + assert(temp.xzstate() == Spectral && temp.ystate() == Spectral); + temp.setToZero(); + Eigen::VectorXd b; + int N = field2vector_size(u); + int Kx = temp.kxmaxDealiased(); + int Kz = temp.kzmaxDealiased(); + int Ny = temp.Ny(); + b = a.topRows(N); + vector2field(b, u); + + int pos = N; + double reval, imval; + Complex val; + + if (temp.taskid() == temp.task_coeff(0, 0)) + for (int ny = 0; ny < Ny; ++ny) + temp.cmplx(0, ny, 0, 0) = Complex(a(pos++), 0); + + for (int kx = 1; kx <= Kx; ++kx) { + int mx = temp.mx(kx); + if (temp.taskid() == temp.task_coeff(mx, 0)) { + for (int ny = 0; ny < Ny; ++ny) { + reval = a(pos++); + imval = a(pos++); + temp.cmplx(mx, ny, 0, 0) = Complex(reval, imval); + } + } + + // ------------------------------------------------------ + // Now copy conjugates of u(kx,ny,0,i) to u(-kx,ny,0,i). These are + // redundant modes stored only for the convenience of FFTW. + int mxm = temp.mx(-kx); + int send_id = temp.task_coeff(mx, 0); + int rec_id = temp.task_coeff(mxm, 0); + for (int ny = 0; ny < Ny; ++ny) { + if (temp.taskid() == send_id && send_id == rec_id) { // all is on the same process -> just copy + temp.cmplx(mxm, ny, 0, 0) = conj(temp.cmplx(mx, ny, 0, 0)); + } +#ifdef HAVE_MPI // send_id != rec_id requires multiple processes + else { // Transfer the conjugates via MPI + if (temp.taskid() == send_id) { + Complex tmp0 = conj(temp.cmplx(mx, ny, 0, 0)); + MPI_Send(&tmp0, 1, MPI_DOUBLE_COMPLEX, rec_id, 0, MPI_COMM_WORLD); + } + if (u.taskid() == rec_id) { + Complex tmp0; + MPI_Status status; + MPI_Recv(&tmp0, 1, MPI_DOUBLE_COMPLEX, send_id, 0, MPI_COMM_WORLD, &status); + temp.cmplx(mxm, ny, 0, 0) = tmp0; + } + } +#endif + } + } + for (int kz = 1; kz <= Kz; ++kz) { + int mz = temp.mz(kz); + if (temp.taskid() == temp.task_coeff(0, mz)) { + for (int ny = 0; ny < Ny; ++ny) { + reval = a(pos++); + imval = a(pos++); + temp.cmplx(0, ny, mz, 0) = Complex(reval, imval); + } + } + } + for (int kx = -Kx; kx <= Kx; kx++) { + if (kx == 0) + continue; + int mx = temp.mx(kx); + for (int kz = 1; kz <= Kz; kz++) { + int mz = temp.mz(kz); + if (u.taskid() == u.task_coeff(mx, mz)) { + for (int ny = 0; ny < Ny; ny++) { + reval = a(pos++); + imval = a(pos++); + val = Complex(reval, imval); + temp.cmplx(mx, ny, mz, 0) = val; + } + } + } + } + temp.setPadded(true); +} + +// ILC::ILC() +// : +// DNS(){ +// +// } + +ILC::ILC(const std::vector& fields, const ILCFlags& flags) + : // base class constructor with no arguments is called automatically (see DNS::DNS()) + main_obe_(0), + init_obe_(0) { + main_obe_ = newOBE(fields, flags); + // creates DNSAlgo with ptr of "nse"-daughter type "obe" + main_algorithm_ = newAlgorithm(fields, main_obe_, flags); + if (!main_algorithm_->full() && flags.initstepping != flags.timestepping) { + ILCFlags initflags = flags; + initflags.timestepping = flags.initstepping; + initflags.dt = flags.dt; + init_obe_ = newOBE(fields, flags); + + // creates DNSAlgo with ptr of "nse"-daughter type "obe" + init_algorithm_ = newAlgorithm(fields, init_obe_, initflags); + // Safety check + if (init_algorithm_->Ninitsteps() != 0) + std::cerr << "DNS::DNS(fields, flags) :\n" + << flags.initstepping << " can't initialize " << flags.timestepping + << " since it needs initialization itself.\n"; + } +} + +ILC::~ILC() {} + +const ChebyCoeff& ILC::Tbase() const { + if (main_obe_) + return main_obe_->Tbase(); + else if (init_obe_) + return init_obe_->Tbase(); + else { + std::cerr << "Error in ILC::Tbase():Tbase is currently undefined" << std::endl; + exit(1); + return init_obe_->Tbase(); // to make compiler happy + } +} + +const ChebyCoeff& ILC::Ubase() const { + if (main_obe_) + return main_obe_->Ubase(); + else if (init_obe_) + return init_obe_->Ubase(); + else { + std::cerr << "Error in ILC::Ubase(): Ubase is currently undefined" << std::endl; + exit(1); + return init_obe_->Ubase(); // to make compiler happy + } +} + +const ChebyCoeff& ILC::Wbase() const { + if (main_obe_) + return main_obe_->Wbase(); + else if (init_obe_) + return init_obe_->Wbase(); + else { + std::cerr << "Error in ILC::Wbase(): Wbase is currently undefined" << std::endl; + exit(1); + return init_obe_->Wbase(); // to make compiler happy + } +} + +std::shared_ptr ILC::newOBE(const std::vector& fields, const ILCFlags& flags) { + std::shared_ptr obe(new OBE(fields, flags)); + return obe; +} + +std::shared_ptr ILC::newOBE(const std::vector& fields, const std::vector& base, const ILCFlags& flags) { + std::shared_ptr obe(new OBE(fields, base, flags)); + return obe; +} + +} // namespace channelflow \ No newline at end of file diff --git a/modules/ilc/ilc.h b/modules/ilc/ilc.h new file mode 100644 index 00000000..b70cf535 --- /dev/null +++ b/modules/ilc/ilc.h @@ -0,0 +1,81 @@ +/** + * Main interface to handle DNS of ILC + * + * This file is a part of channelflow version 2.0, https://channelflow.ch . + * License is GNU GPL version 2 or later: ./LICENSE + * + * Original author: Florian Reetz + */ + +#ifndef ILC_H +#define ILC_H + +#include "channelflow/dns.h" +#include "channelflow/dnsalgo.h" +#include "modules/ilc/ilcflags.h" +#include "modules/ilc/obe.h" + +namespace chflow { + +int field2vector_size(const FlowField& u, const FlowField& temp); + +/** \brief Turn the two flowfields for velocity and temperature into one Eigen vector + * \param[in] u velocity field + * \param[in] temp temperature field + * \param[in] x vector for the linear algebra + * + * The vectorization of u is analog to the field2vector, temparture is piped entirely + * into the vector (a single independent dimensions) + */ +void field2vector(const FlowField& u, const FlowField& temp, Eigen::VectorXd& x); + +/** \brief Turn one Eigen vector into the two flowfields for velocity and temperature + * \param[in] x vector for the linear algebra + * \param[in] u velocity field + * \param[in] temp temperature field + * + * The vectorization of u is analog to the field2vector, temperature is piped entirely + * into the vector (a single independent dimension) + */ +void vector2field(const Eigen::VectorXd& x, FlowField& u, FlowField& temp); + +/** \brief extension of the DNSFlags class + * + * VEDNSFlags class, holds all additional parameters for viscoelastic fluids + */ + +/** \brief wrapper class of DNSAlgorithm and ILC + * + * + */ +class ILC : public DNS { + public: + // ILC (); + // ILC (const ILC & ilc); + ILC(const std::vector& fields, const ILCFlags& flags); + // ILC (const vector & fields, const vector & base, + // const ILCFlags & flags); + + virtual ~ILC(); + + ILC& operator=(const ILC& ilc); + + // virtual void advance (vector & fields, int nSteps = 1); + // + // virtual void reset_dt (Real dt); + // virtual void printStack () const; + + const ChebyCoeff& Tbase() const; + const ChebyCoeff& Ubase() const; + const ChebyCoeff& Wbase() const; + + protected: + std::shared_ptr main_obe_; + std::shared_ptr init_obe_; + + std::shared_ptr newOBE(const std::vector& fields, const ILCFlags& flags); + std::shared_ptr newOBE(const std::vector& fields, const std::vector& base, const ILCFlags& flags); +}; + +} // namespace channelflow +#endif // ILC_H \ No newline at end of file diff --git a/modules/ilc/obe.cpp b/modules/ilc/obe.cpp index b834cc17..57189d66 100644 --- a/modules/ilc/obe.cpp +++ b/modules/ilc/obe.cpp @@ -1,6 +1,6 @@ /** - * This file is a part of channelflow version 2.0. - * License is GNU GPL version 2 or later: https://channelflow.org/license + * This file is a part of channelflow version 2.0, https://channelflow.ch . + * License is GNU GPL version 2 or later: ./LICENSE * * Original author: Florian Reetz */ diff --git a/modules/ilc/obe.h b/modules/ilc/obe.h index 2599296d..e6daee46 100644 --- a/modules/ilc/obe.h +++ b/modules/ilc/obe.h @@ -1,11 +1,11 @@ /** - * System class of Oberbeck-Boussinesq equations for standard channel flows - * - * This file is a part of channelflow version 2.0. - * License is GNU GPL version 2 or later: https://channelflow.org/license - * - * Original author: Florian Reetz - */ +* System class of Oberbeck-Boussinesq equations for standard channel flows +* +* This file is a part of channelflow version 2.0, https://channelflow.ch . +* License is GNU GPL version 2 or later: ./LICENSE +* +* Original author: Florian Reetz +*/ #ifndef OBE_H #define OBE_H From 2ed0c75add0857c4e2850e187aee6d9bc17367f4 Mon Sep 17 00:00:00 2001 From: Florian Reetz Date: Thu, 7 May 2020 14:18:16 +0200 Subject: [PATCH 04/10] apply clang-format to the 6 added ilc-files --- modules/ilc/ilc.cpp | 10 +++++----- modules/ilc/ilc.h | 5 +++-- modules/ilc/ilcflags.cpp | 8 ++++---- modules/ilc/ilcflags.h | 5 +++-- modules/ilc/obe.cpp | 2 +- modules/ilc/obe.h | 16 ++++++++-------- 6 files changed, 24 insertions(+), 22 deletions(-) diff --git a/modules/ilc/ilc.cpp b/modules/ilc/ilc.cpp index 50f61625..4fd16e27 100644 --- a/modules/ilc/ilc.cpp +++ b/modules/ilc/ilc.cpp @@ -7,7 +7,6 @@ #include "modules/ilc/ilc.h" - namespace chflow { int field2vector_size(const FlowField& u, const FlowField& temp) { @@ -241,8 +240,8 @@ ILC::ILC(const std::vector& fields, const ILCFlags& flags) // Safety check if (init_algorithm_->Ninitsteps() != 0) std::cerr << "DNS::DNS(fields, flags) :\n" - << flags.initstepping << " can't initialize " << flags.timestepping - << " since it needs initialization itself.\n"; + << flags.initstepping << " can't initialize " << flags.timestepping + << " since it needs initialization itself.\n"; } } @@ -289,9 +288,10 @@ std::shared_ptr ILC::newOBE(const std::vector& fields, const ILC return obe; } -std::shared_ptr ILC::newOBE(const std::vector& fields, const std::vector& base, const ILCFlags& flags) { +std::shared_ptr ILC::newOBE(const std::vector& fields, const std::vector& base, + const ILCFlags& flags) { std::shared_ptr obe(new OBE(fields, base, flags)); return obe; } -} // namespace channelflow \ No newline at end of file +} // namespace chflow \ No newline at end of file diff --git a/modules/ilc/ilc.h b/modules/ilc/ilc.h index b70cf535..6efd0f91 100644 --- a/modules/ilc/ilc.h +++ b/modules/ilc/ilc.h @@ -74,8 +74,9 @@ class ILC : public DNS { std::shared_ptr init_obe_; std::shared_ptr newOBE(const std::vector& fields, const ILCFlags& flags); - std::shared_ptr newOBE(const std::vector& fields, const std::vector& base, const ILCFlags& flags); + std::shared_ptr newOBE(const std::vector& fields, const std::vector& base, + const ILCFlags& flags); }; -} // namespace channelflow +} // namespace chflow #endif // ILC_H \ No newline at end of file diff --git a/modules/ilc/ilcflags.cpp b/modules/ilc/ilcflags.cpp index e0073d11..db8356d7 100644 --- a/modules/ilc/ilcflags.cpp +++ b/modules/ilc/ilcflags.cpp @@ -63,9 +63,9 @@ ILCFlags::ILCFlags(ArgList& args, const bool laurette) { // also needed for ILC but better show at the end const std::string tsymmstr = args.getstr("-tsymms", "--tempsymmetries", "", - "constrain temp(t) to invariant " - "symmetric subspace, argument is the filename for a file " - "listing the generators of the isotropy group"); + "constrain temp(t) to invariant " + "symmetric subspace, argument is the filename for a file " + "listing the generators of the isotropy group"); const Real ystats_ = args.getreal("-ys", "--ystats", 0, "y-coordinate of height dependent statistics, e.g. Nu(y)"); // set flags @@ -137,4 +137,4 @@ void ILCFlags::load(int taskid, const std::string indir) { ystats = getRealfromLine(taskid, is); } -} // namespace channelflow +} // namespace chflow diff --git a/modules/ilc/ilcflags.h b/modules/ilc/ilcflags.h index c74f6589..ed95d8c6 100644 --- a/modules/ilc/ilcflags.h +++ b/modules/ilc/ilcflags.h @@ -1,6 +1,7 @@ /** * Control parameters for time-integration within the ILC module - * ILCFlags specifies all relevant parameters for integrating the Oberbeck-Boussinesq equations in doubly periodic channel domains. + * ILCFlags specifies all relevant parameters for integrating the Oberbeck-Boussinesq equations in doubly periodic + * channel domains. * * This file is a part of channelflow version 2.0, https://channelflow.ch . * License is GNU GPL version 2 or later: ./LICENSE @@ -59,5 +60,5 @@ class ILCFlags : public DNSFlags { virtual void load(int taskid, const std::string indir) override; }; -} // namespace channelflow +} // namespace chflow #endif // ILCFLAGS_H \ No newline at end of file diff --git a/modules/ilc/obe.cpp b/modules/ilc/obe.cpp index 57189d66..50c1ecb5 100644 --- a/modules/ilc/obe.cpp +++ b/modules/ilc/obe.cpp @@ -807,4 +807,4 @@ ChebyCoeff hydrostaticPressureGradientY(ChebyCoeff Tbase, ILCFlags flags) { return Py; } -} // namespace channelflow \ No newline at end of file +} // namespace chflow \ No newline at end of file diff --git a/modules/ilc/obe.h b/modules/ilc/obe.h index e6daee46..6a141f77 100644 --- a/modules/ilc/obe.h +++ b/modules/ilc/obe.h @@ -1,11 +1,11 @@ /** -* System class of Oberbeck-Boussinesq equations for standard channel flows -* -* This file is a part of channelflow version 2.0, https://channelflow.ch . -* License is GNU GPL version 2 or later: ./LICENSE -* -* Original author: Florian Reetz -*/ + * System class of Oberbeck-Boussinesq equations for standard channel flows + * + * This file is a part of channelflow version 2.0, https://channelflow.ch . + * License is GNU GPL version 2 or later: ./LICENSE + * + * Original author: Florian Reetz + */ #ifndef OBE_H #define OBE_H @@ -101,5 +101,5 @@ ChebyCoeff linearTemperatureProfile(Real a, Real b, int Ny, ILCFlags flags); Real hydrostaticPressureGradientX(ILCFlags flags); ChebyCoeff hydrostaticPressureGradientY(ChebyCoeff Tbase, ILCFlags flags); -} // namespace channelflow +} // namespace chflow #endif \ No newline at end of file From 56e9ac43e52c9903e3472ca142bb33aed4af018f Mon Sep 17 00:00:00 2001 From: Florian Reetz Date: Thu, 7 May 2020 17:11:21 +0200 Subject: [PATCH 05/10] Added the DSI interface for ILC. --- modules/ilc/CMakeLists.txt | 2 + modules/ilc/ilcdsi.cpp | 1266 ++++++++++++++++++++++++++++++++++++ modules/ilc/ilcdsi.h | 146 +++++ 3 files changed, 1414 insertions(+) create mode 100644 modules/ilc/ilcdsi.cpp create mode 100644 modules/ilc/ilcdsi.h diff --git a/modules/ilc/CMakeLists.txt b/modules/ilc/CMakeLists.txt index eb750773..7c38a993 100644 --- a/modules/ilc/CMakeLists.txt +++ b/modules/ilc/CMakeLists.txt @@ -9,6 +9,7 @@ set( ${CMAKE_CURRENT_SOURCE_DIR}/ilcflags.cpp ${CMAKE_CURRENT_SOURCE_DIR}/obe.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ilc.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ilcdsi.cpp ) set( @@ -16,6 +17,7 @@ set( ${CMAKE_CURRENT_SOURCE_DIR}/ilcflags.h ${CMAKE_CURRENT_SOURCE_DIR}/obe.h ${CMAKE_CURRENT_SOURCE_DIR}/ilc.h + ${CMAKE_CURRENT_SOURCE_DIR}/ilcdsi.h ) # Define the target with appropriate dependencies diff --git a/modules/ilc/ilcdsi.cpp b/modules/ilc/ilcdsi.cpp new file mode 100644 index 00000000..56e8093e --- /dev/null +++ b/modules/ilc/ilcdsi.cpp @@ -0,0 +1,1266 @@ +/** + * This file is a part of channelflow version 2.0, https://channelflow.ch . + * License is GNU GPL version 2 or later: ./LICENSE + * + * Original author: Florian Reetz + */ + +#include "modules/ilc/ilcdsi.h" +#ifdef HAVE_MPI +#include +#endif + +#include "cfbasics/mathdefs.h" +#include "channelflow/diffops.h" +// #include "viscoelastic/veutils.h" +#include "modules/ilc/ilc.h" + +using namespace std; + +namespace chflow { + +/*utility functions*/ + +std::vector ilcstats(const FlowField& u, const FlowField& temp, const ILCFlags flags) { + double l2n = L2Norm(u); + if (std::isnan(l2n)) { + cferror("L2Norm(u) is nan"); + } + + FlowField u_tot = totalVelocity(u, flags); + FlowField temp_tot = totalTemperature(temp, flags); + + std::vector stats; + stats.push_back(L2Norm(u)); + stats.push_back(heatcontent(temp_tot, flags)); + stats.push_back(L2Norm(u_tot)); + stats.push_back(L2Norm(temp_tot)); + stats.push_back(L2Norm3d(u)); + stats.push_back(Ecf(u)); + stats.push_back(getUbulk(u)); + stats.push_back(getWbulk(u)); + stats.push_back(wallshear(u_tot)); + stats.push_back(buoyPowerInput(u_tot, temp_tot, flags)); + stats.push_back(dissipation(u_tot, flags)); + stats.push_back(heatinflux(temp_tot, flags)); + stats.push_back(L2Norm(temp)); + stats.push_back(Nusselt_plane(u_tot, temp_tot, flags)); + return stats; +} + +string ilcfieldstatsheader(const ILCFlags flags) { + stringstream header; + header << setw(14) << "L2(u')" << setw(10) << "(y=" << flags.ystats << ")" // change position with L2(T') + << setw(14) << "L2(u)" << setw(14) << "L2(T)" << setw(14) << "e3d" << setw(14) << "ecf" << setw(14) + << "ubulk" << setw(14) << "wbulk" << setw(14) << "wallshear" << setw(14) << "buoyPowIn" << setw(14) + << "totalDiss" << setw(14) << "heatinflux" << setw(14) << "L2(T')" << setw(10) << "Nu(y=" << flags.ystats + << ")"; + return header.str(); +} + +string ilcfieldstatsheader_t(const string tname, const ILCFlags flags) { + stringstream header; + header << setw(6) << "#(" << tname << ")" << ilcfieldstatsheader(flags); + return header.str(); +} + +string ilcfieldstats(const FlowField& u, const FlowField& temp, const ILCFlags flags) { + std::vector stats = ilcstats(u, temp, flags); + // Return string + stringstream s; + for (uint i = 0; i < stats.size(); i++) { + s << setw(14) << stats[i]; + } + return s.str(); +} + +string ilcfieldstats_t(const FlowField& u, const FlowField& temp, const Real t, const ILCFlags flags) { + std::vector stats = ilcstats(u, temp, flags); + // Return string + stringstream s; + s << setw(8) << t; + for (uint i = 0; i < stats.size(); i++) { + s << setw(14) << stats[i]; + } + return s.str(); +} + +FlowField totalVelocity(const FlowField& velo, const ILCFlags flags) { + // copy + FlowField u(velo); + FlowField tmp(u.Nx(), u.Ny(), u.Nz(), 1, u.Lx(), u.Lz(), u.a(), u.b(), u.cfmpi()); + + // get base flow + ILC ilc({u, tmp, tmp}, flags); + ChebyCoeff Ubase = ilc.Ubase(); + ChebyCoeff Wbase = ilc.Wbase(); + + // add base flow (should be identical to code in function temperatureNL in OBE + for (int ny = 0; ny < u.Ny(); ++ny) { + if (u.taskid() == u.task_coeff(0, 0)) { + u.cmplx(0, ny, 0, 0) += Complex(Ubase(ny), 0.0); + u.cmplx(0, ny, 0, 2) += Complex(Wbase(ny), 0.0); + } + } + if (u.taskid() == u.task_coeff(0, 0)) { + u.cmplx(0, 0, 0, 1) -= Complex(flags.Vsuck, 0.); + } + + return u; +} + +FlowField totalTemperature(const FlowField& temp, const ILCFlags flags) { + // copy + FlowField T(temp); + FlowField tmp(T.Nx(), T.Ny(), T.Nz(), 3, T.Lx(), T.Lz(), T.a(), T.b(), T.cfmpi()); + + // get base flow + ILC ilc({tmp, T, T}, flags); + ChebyCoeff Tbase = ilc.Tbase(); + + // add base flow (should be identical to code in function temperatureNL in OBE + for (int ny = 0; ny < T.Ny(); ++ny) { + if (T.taskid() == T.task_coeff(0, 0)) + T.cmplx(0, ny, 0, 0) += Complex(Tbase(ny), 0.0); + } + + return T; +} + +Real buoyPowerInput(const FlowField& utot, const FlowField& ttot, const ILCFlags flags, bool relative) { + // calculate the bouyancy force along the velocity field to get + // the power input to the kinetic energy equation + + // get parameters + Real nu = flags.nu; + Real sing = sin(flags.gammax); + Real cosg = cos(flags.gammax); + Real grav = flags.grav; + Real laminarInput = grav * sing * sing / (720 * nu); // normalized by Volume + + // prepare loop over field + FlowField u(utot); + FlowField T(ttot); + FlowField xinput(T.Nx(), T.Ny(), T.Nz(), T.Nd(), T.Lx(), T.Lz(), T.a(), T.b(), T.cfmpi(), Physical, Physical); + FlowField yinput(T.Nx(), T.Ny(), T.Nz(), T.Nd(), T.Lx(), T.Lz(), T.a(), T.b(), T.cfmpi(), Physical, Physical); + lint Nz = u.Nz(); + lint nxlocmin = u.nxlocmin(); + lint nxlocmax = u.nxlocmin() + u.Nxloc(); + lint nylocmin = u.nylocmin(); + lint nylocmax = u.nylocmax(); + + // sum up buoyancy term + u.makePhysical(); + T.makePhysical(); +#ifdef HAVE_MPI + for (lint nx = nxlocmin; nx < nxlocmax; ++nx) + for (lint nz = 0; nz < Nz; ++nz) + for (lint ny = nylocmin; ny < nylocmax; ++ny) { + xinput(nx, ny, nz, 0) = u(nx, ny, nz, 0) * T(nx, ny, nz, 0); + yinput(nx, ny, nz, 0) = u(nx, ny, nz, 1) * T(nx, ny, nz, 0); + } +#else + for (lint ny = nylocmin; ny < nylocmax; ++ny) + for (lint nx = nxlocmin; nx < nxlocmax; ++nx) + for (lint nz = 0; nz < Nz; ++nz) { + xinput(nx, ny, nz, 0) = u(nx, ny, nz, 0) * T(nx, ny, nz, 0); + yinput(nx, ny, nz, 0) = u(nx, ny, nz, 1) * T(nx, ny, nz, 0); + } +#endif + xinput.makeSpectral(); + yinput.makeSpectral(); + + // calculate the input mean with cheby profile (code is taken from OBE::initConstraint) + ChebyCoeff xprof(T.My(), T.a(), T.b(), Spectral); + ChebyCoeff yprof(T.My(), T.a(), T.b(), Spectral); + Real xtmp = 0; + Real ytmp = 0; + for (int ny = 0; ny < T.My(); ++ny) { + if (utot.taskid() == 0) { + xtmp = sing * Re(xinput.cmplx(0, ny, 0, 0)); + ytmp = cosg * Re(yinput.cmplx(0, ny, 0, 0)); + } +#ifdef HAVE_MPI + MPI_Bcast(&xtmp, 1, MPI_DOUBLE, utot.task_coeff(0, 0), *utot.comm_world()); + MPI_Bcast(&ytmp, 1, MPI_DOUBLE, utot.task_coeff(0, 0), *utot.comm_world()); +#endif + xprof[ny] = xtmp; + yprof[ny] = ytmp; + } + + Real buoyancyInput = xprof.mean() + yprof.mean(); + // difference between full input and laminar input + if (relative && abs(laminarInput) > 1e-12) { + buoyancyInput *= 1.0 / laminarInput; + buoyancyInput -= 1; + } + + return buoyancyInput; +} + +Real dissipation(const FlowField& utot, const ILCFlags flags, bool normalize, bool relative) { + Real diss = flags.nu * dissipation(utot, normalize); + + // analytic laminar dissipation (for standard base flow) + Real sing = sin(flags.gammax); + Real laminarDiss = flags.grav * sing * sing / (720 * flags.nu); // normalized by Volume + // difference between full input and laminar input + if (relative && abs(laminarDiss) > 1e-12) { + diss *= 1.0 / laminarDiss; + diss -= 1; + } + + return diss; +} + +Real heatinflux(const FlowField& temp, const ILCFlags flags, bool normalize, bool relative) { + // with reference to wallshear, but only lower wall + assert(temp.ystate() == Spectral); + Real I = 0; + if (temp.taskid() == temp.task_coeff(0, 0)) { + ChebyCoeff tprof = Re(temp.profile(0, 0, 0)); + ChebyCoeff dTdy = diff(tprof); + I = flags.kappa * abs(dTdy.eval_a()); + } +#ifdef HAVE_MPI + MPI_Bcast(&I, 1, MPI_DOUBLE, temp.task_coeff(0, 0), temp.cfmpi()->comm_world); +#endif + if (!normalize) + I *= 2 * temp.Lx() * temp.Lz(); + if (relative) { + Real kappa = flags.kappa; + Real deltaT = flags.tlowerwall - flags.tupperwall; + Real H = temp.b() - temp.a(); + I *= 1.0 / kappa / deltaT * H; + I -= 1.0; + } + return I; +} + +Real heatcontent(const FlowField& ttot, const ILCFlags flags) { + assert(ttot.ystate() == Spectral); + int N = 100; + Real dy = (flags.ystats - ttot.a()) / (N - 1); + Real avt = 0; // average temperature + if (ttot.taskid() == ttot.task_coeff(0, 0)) { + ChebyCoeff tprof = Re(ttot.profile(0, 0, 0)); + for (int i = 0; i < N; ++i) { + Real y = ttot.a() + i * dy; + avt += tprof.eval(y); + } + avt *= 1.0 / N; + } +#ifdef HAVE_MPI + MPI_Bcast(&avt, 1, MPI_DOUBLE, ttot.task_coeff(0, 0), ttot.cfmpi()->comm_world); +#endif + return avt; +} + +Real Nusselt_plane(const FlowField& utot, const FlowField& ttot, const ILCFlags flags, bool relative) { + assert(utot.ystate() == Spectral && ttot.ystate() == Spectral); + + // calculate product for advective heat transport + FlowField u(utot); + FlowField T(ttot); + FlowField vt(T.Nx(), T.Ny(), T.Nz(), T.Nd(), T.Lx(), T.Lz(), T.a(), T.b(), T.cfmpi(), Physical, Physical); + lint Nz = u.Nz(); + lint nxlocmin = u.nxlocmin(); + lint nxlocmax = u.nxlocmin() + u.Nxloc(); + lint nylocmin = u.nylocmin(); + lint nylocmax = u.nylocmax(); + + // loop to form product + u.makePhysical(); + T.makePhysical(); +#ifdef HAVE_MPI + for (lint nx = nxlocmin; nx < nxlocmax; ++nx) + for (lint nz = 0; nz < Nz; ++nz) + for (lint ny = nylocmin; ny < nylocmax; ++ny) { + vt(nx, ny, nz, 0) = u(nx, ny, nz, 1) * T(nx, ny, nz, 0); + } +#else + for (lint ny = nylocmin; ny < nylocmax; ++ny) + for (lint nx = nxlocmin; nx < nxlocmax; ++nx) + for (lint nz = 0; nz < Nz; ++nz) { + vt(nx, ny, nz, 0) = u(nx, ny, nz, 1) * T(nx, ny, nz, 0); + } +#endif + vt.makeSpectral(); + + // calculate Nusselt number + Real Nu = 0; + Real y = flags.ystats; + Real kappa = flags.kappa; + if (ttot.taskid() == ttot.task_coeff(0, 0)) { + ChebyCoeff tprof = Re(ttot.profile(0, 0, 0)); + ChebyCoeff dTdy = diff(tprof); + ChebyCoeff vtprof = Re(vt.profile(0, 0, 0)); + Nu = vtprof.eval(y) - + kappa * dTdy.eval(y); // Formula 10 in Chilla&Schumacher 2012 (together with normalization below) + } + +#ifdef HAVE_MPI + MPI_Bcast(&Nu, 1, MPI_DOUBLE, ttot.task_coeff(0, 0), ttot.cfmpi()->comm_world); +#endif + + if (relative) { + Real deltaT = flags.tlowerwall - flags.tupperwall; + Real H = utot.b() - utot.a(); + Nu *= 1.0 / kappa / deltaT * H; + Nu -= 1.0; + } + return Nu; +} + +/* Begin of ilcDSI class*/ + +ilcDSI::ilcDSI() {} + +ilcDSI::ilcDSI(ILCFlags& ilcflags, FieldSymmetry sigma, PoincareCondition* h, TimeStep dt, bool Tsearch, bool xrelative, + bool zrelative, bool Tnormalize, Real Unormalize, const FlowField& u, const FlowField& temp, ostream* os) + : cfDSI(ilcflags, sigma, h, dt, Tsearch, xrelative, zrelative, Tnormalize, Unormalize, u, os), + ilcflags_(ilcflags) {} + +Eigen::VectorXd ilcDSI::eval(const Eigen::VectorXd& x) { + FlowField u(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField temp(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + + Real T; + extractVectorILC(x, u, temp, sigma_, T); + + FlowField Gu(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField Gtemp(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + G(u, temp, T, h_, sigma_, Gu, Gtemp, ilcflags_, dt_, Tnormalize_, Unormalize_, fcount_, CFL_, *os_); + Eigen::VectorXd Gx(Eigen::VectorXd::Zero(x.rows())); + // Galpha *= 1./vednsflags_.b_para; + field2vector(Gu, Gtemp, Gx); // This does not change the size of Gx and automatically leaves the last entries zero + + return Gx; +} + +Eigen::VectorXd ilcDSI::eval(const Eigen::VectorXd& x0, const Eigen::VectorXd& x1, bool symopt) { + FlowField u0(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField u1(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField temp0(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField temp1(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + Real T0, T1; + FieldSymmetry sigma0, sigma1; + extractVectorILC(x0, u0, temp0, sigma0, T0); + extractVectorILC(x1, u1, temp1, sigma1, T1); + + FlowField Gu(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField Gtemp(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + + f(u0, temp0, T0, h_, Gu, Gtemp, ilcflags_, dt_, fcount_, CFL_, *os_); + if (symopt) { + Gu *= sigma0; + if (sigma0.sy() == -1) { + // wall-normal mirroring in velocity requires sign change in temperature + FieldSymmetry inv(-1); + sigma0 *= inv; + } + Gtemp *= sigma0; + } + Gu -= u1; + Gtemp -= temp1; + + // normalize + if (Tnormalize_) { + Gu *= 1.0 / T0; + Gtemp *= 1.0 / T0; + } + if (Unormalize_ != 0.0) { + Real funorm = L2Norm3d(Gu); + Gu *= 1. / sqrt(abs(funorm * (Unormalize_ - funorm))); + // u should stay off zero, so normalize with u for now - temp should also stay away from zero + Gtemp *= 1. / sqrt(abs(funorm * (Unormalize_ - funorm))); + } + + Eigen::VectorXd Gx(Eigen::VectorXd::Zero(x0.rows())); + field2vector(Gu, Gtemp, Gx); // This does not change the size of Gx and automatically leaves the last entries zero + + return Gx; +} + +void ilcDSI::save(const Eigen::VectorXd& x, const string filebase, const string outdir, const bool fieldsonly) { + FlowField u(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField temp(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + FieldSymmetry sigma; + Real T; + extractVectorILC(x, u, temp, sigma, T); + + u.save(outdir + "u" + filebase); + temp.save(outdir + "t" + filebase); + + if (!fieldsonly) { + string fs = ilcfieldstats(u, temp, ilcflags_); + if (u.taskid() == 0) { + if (xrelative_ || zrelative_ || !sigma.isIdentity()) + sigma.save(outdir + "sigma" + filebase); + if (Tsearch_) + chflow::save(T, outdir + "T" + filebase); + // sigma.save (outdir+"sigmaconverge.asc", ios::app); + ofstream fout((outdir + "fieldconverge.asc").c_str(), ios::app); + long pos = fout.tellp(); + if (pos == 0) + fout << ilcfieldstatsheader() << endl; + fout << fs << endl; + fout.close(); + ilcflags_.save(outdir); + } + } +} + +string ilcDSI::stats(const Eigen::VectorXd& x) { + FlowField u(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField temp(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + FieldSymmetry sigma; + Real T; + extractVectorILC(x, u, temp, sigma, T); + return ilcfieldstats_t(u, temp, mu_, ilcflags_); +} + +pair ilcDSI::stats_minmax(const Eigen::VectorXd& x) { + FlowField u(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField temp(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField Gu(u); + FlowField Gtemp(temp); + FieldSymmetry sigma; + Real T; + extractVectorILC(x, u, temp, sigma, T); + + std::vector stats = ilcstats(u, temp, ilcflags_); + std::vector minstats(stats); + std::vector maxstats(stats); + + // quick hack to avoid new interface or creating simple f() for ILC + TimeStep dt = TimeStep(ilcflags_.dt, 0, 1, 1, 0, 0, false); + int fcount = 0; + PoincareCondition* h = 0; + Real CFL = 0.0; + std::ostream muted_os(0); + Real timep = T / 100.0; + + *os_ << "Using flag -orbOut: Calculate minmax-statistics of periodic orbit." << endl; + for (int t = 0; t < 100; t++) { + f(u, temp, timep, h, Gu, Gtemp, ilcflags_, dt, fcount, CFL, muted_os); + stats = ilcstats(Gu, Gtemp, ilcflags_); + for (uint i = 0; i < stats.size(); i++) { + minstats[i] = (minstats[i] < stats[i]) ? minstats[i] : stats[i]; + maxstats[i] = (maxstats[i] > stats[i]) ? maxstats[i] : stats[i]; + } + u = Gu; + temp = Gtemp; + } + // Return string + stringstream smin; + stringstream smax; + smin << setw(8) << mu_; + smax << setw(8) << mu_; + for (uint i = 0; i < stats.size(); i++) { + smin << setw(14) << minstats[i]; + smax << setw(14) << maxstats[i]; + } + + pair minmax; + minmax = make_pair(smin.str(), smax.str()); + return minmax; +} + +string ilcDSI::statsHeader() { return ilcfieldstatsheader_t(ilc_cPar2s(ilc_cPar_), ilcflags_); } + +/// after finding new solution fix phases +void ilcDSI::phaseShift(Eigen::VectorXd& x) { + if (xphasehack_ || zphasehack_) { + FlowField unew(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField tnew(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + FieldSymmetry sigma; + Real T; + extractVectorILC(x, unew, tnew, sigma, T); + // vector2field (x,unew); + const int phasehackcoord = 0; // Those values were fixed in continuesoln anyway + const parity phasehackparity = Odd; + const Real phasehackguess = 0.0; + + if (zphasehack_) { + FieldSymmetry tau = zfixphasehack(unew, phasehackguess, phasehackcoord, phasehackparity); + cout << "fixing z phase of potential solution with phase shift tau == " << tau << endl; + unew *= tau; + tnew *= tau; + } + if (xphasehack_) { + FieldSymmetry tau = xfixphasehack(unew, phasehackguess, phasehackcoord, phasehackparity); + cout << "fixing x phase of potential solution with phase shift tau == " << tau << endl; + unew *= tau; + tnew *= tau; + } + if (uUbasehack_) { + cout << "fixing u+Ubase decomposition so that = 0 at walls (i.e. Ubase balances mean pressure " + "gradient))" + << endl; + Real ubulk = Re(unew.profile(0, 0, 0)).mean(); + if (abs(ubulk) < 1e-15) + ubulk = 0.0; + + ChebyCoeff Ubase = laminarProfile(ilcflags_.nu, ilcflags_.constraint, ilcflags_.dPdx, + ilcflags_.Ubulk - ubulk, ilcflags_.Vsuck, unew.a(), unew.b(), + ilcflags_.ulowerwall, ilcflags_.uupperwall, unew.Ny()); + + fixuUbasehack(unew, Ubase); + } + makeVectorILC(unew, tnew, sigma, T, x); + } +} + +void ilcDSI::phaseShift(Eigen::MatrixXd& y) { + if (xphasehack_ || zphasehack_) { + FlowField unew(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField tnew(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + Eigen::VectorXd yvec; + FieldSymmetry sigma; + Real T; + + const int phasehackcoord = 0; // Those values were fixed in continuesoln anyway + const parity phasehackparity = Odd; + const Real phasehackguess = 0.0; + + FieldSymmetry taux(0.0, 0.0); + FieldSymmetry tauz(0.0, 0.0); + + extractVectorILC(y.col(0), unew, tnew, sigma, T); + + if (xphasehack_) { + taux = xfixphasehack(unew, phasehackguess, phasehackcoord, phasehackparity); + cout << "fixing x phase of potential solution with phase shift tau == " << taux << endl; + } + if (zphasehack_) { + tauz = zfixphasehack(unew, phasehackguess, phasehackcoord, phasehackparity); + cout << "fixing z phase of potential solution with phase shift tau == " << tauz << endl; + } + + for (int i = 0; i < y.cols(); i++) { + extractVectorILC(y.col(i), unew, tnew, sigma, T); + unew *= taux; + tnew *= taux; + unew *= tauz; + tnew *= tauz; + makeVectorILC(unew, tnew, sigma, T, yvec); + y.col(i) = yvec; + } + } +} + +Real ilcDSI::extractT(const Eigen::VectorXd& x) { // inefficient hack + FlowField u(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField temp(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + FieldSymmetry sigma; + Real T; + extractVectorILC(x, u, temp, sigma, T); + return T; +} + +Real ilcDSI::extractXshift(const Eigen::VectorXd& x) { // inefficient hack + FlowField u(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField temp(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + FieldSymmetry sigma; + Real T; + extractVectorILC(x, u, temp, sigma, T); + return sigma.ax(); +} + +Real ilcDSI::extractZshift(const Eigen::VectorXd& x) { // inefficient hack + FlowField u(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField temp(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + FieldSymmetry sigma; + Real T; + extractVectorILC(x, u, temp, sigma, T); + return sigma.az(); +} + +void ilcDSI::makeVectorILC(const FlowField& u, const FlowField& temp, const FieldSymmetry& sigma, const Real T, + Eigen::VectorXd& x) { + if (u.Nd() != 3) + cferror("ilcDSI::makeVector(): u.Nd() = " + i2s(u.Nd()) + " != 3"); + if (temp.Nd() != 1) + cferror("ilcDSI::makeVector(): temp.Nd() = " + i2s(temp.Nd()) + " != 1"); + int taskid = u.taskid(); + + int uunk = field2vector_size(u, temp); // # of variables for u and alpha unknonwn + const int Tunk = (Tsearch_ && taskid == 0) ? uunk : -1; // index for T unknown + const int xunk = (xrelative_ && taskid == 0) ? uunk + Tsearch_ : -1; + const int zunk = (zrelative_ && taskid == 0) ? uunk + Tsearch_ + xrelative_ : -1; + int Nunk = (taskid == 0) ? uunk + Tsearch_ + xrelative_ + zrelative_ : uunk; + if (x.rows() < Nunk) + x.resize(Nunk); + field2vector(u, temp, x); + if (taskid == 0) { + if (Tsearch_) + x(Tunk) = T; + if (xrelative_) + x(xunk) = sigma.ax(); + if (zrelative_) + x(zunk) = sigma.az(); + } +} + +void ilcDSI::extractVectorILC(const Eigen::VectorXd& x, FlowField& u, FlowField& temp, FieldSymmetry& sigma, Real& T) { + int uunk = field2vector_size(u, temp); // number of components in x that corresond to u and alpha + vector2field(x, u, temp); + const int Tunk = uunk + Tsearch_ - 1; + const int xunk = uunk + Tsearch_ + xrelative_ - 1; + const int zunk = uunk + Tsearch_ + xrelative_ + zrelative_ - 1; + Real ax = 0; + Real az = 0; + if (u.taskid() == 0) { + T = Tsearch_ ? x(Tunk) : Tinit_; + ax = xrelative_ ? x(xunk) : axinit_; + az = zrelative_ ? x(zunk) : azinit_; + } +#ifdef HAVE_MPI + MPI_Bcast(&T, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(&ax, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(&az, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); +#endif + sigma = FieldSymmetry(sigma_.sx(), sigma_.sy(), sigma_.sz(), ax, az, sigma_.s()); +} + +Eigen::VectorXd ilcDSI::xdiff(const Eigen::VectorXd& a) { + FlowField u(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField temp(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + vector2field(a, u, temp); + Eigen::VectorXd dadx(a.size()); + dadx.setZero(); + u = chflow::xdiff(u); + temp = chflow::xdiff(temp); + field2vector(u, temp, dadx); + dadx *= 1. / L2Norm(dadx); + return dadx; +} + +Eigen::VectorXd ilcDSI::zdiff(const Eigen::VectorXd& a) { + FlowField u(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField temp(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + vector2field(a, u, temp); + Eigen::VectorXd dadz(a.size()); + dadz.setZero(); + u = chflow::zdiff(u); + temp = chflow::zdiff(temp); + field2vector(u, temp, dadz); + dadz *= 1. / L2Norm(dadz); + return dadz; +} + +Eigen::VectorXd ilcDSI::tdiff(const Eigen::VectorXd& a, Real epsDt) { + FlowField u(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField temp(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + FieldSymmetry sigma; + Real T; + // quick hack to avoid new interface or creating simple f() for ILC + TimeStep dt = TimeStep(epsDt, 0, 1, 1, 0, 0, false); + int fcount = 0; + PoincareCondition* h = 0; + Real CFL = 0.0; + FlowField edudtf(u); + FlowField edtempdtf(temp); + std::ostream muted_os(0); + // vector2field (a, u, temp); + extractVectorILC(a, u, temp, sigma, T); + // use existing f() instead of simple + f(u, temp, epsDt, h, edudtf, edtempdtf, ilcflags_, dt, fcount, CFL, muted_os); + // f (temp, 1,epsDt, edtempdtf, dnsflags_, *os_); + edudtf -= u; + edtempdtf -= temp; + Eigen::VectorXd dadt(a.size()); + field2vector(edudtf, edtempdtf, dadt); + dadt *= 1. / L2Norm(dadt); + return dadt; +} + +void ilcDSI::updateMu(Real mu) { + DSI::updateMu(mu); + if (ilc_cPar_ == ilc_continuationParameter::none) { + cfDSI::updateMu(mu); + } else if (ilc_cPar_ == ilc_continuationParameter::Ra) { + Real Prandtl = ilcflags_.nu / ilcflags_.kappa; + ilcflags_.nu = sqrt(Prandtl / mu); + ilcflags_.kappa = 1.0 / sqrt(Prandtl * mu); + } else if (ilc_cPar_ == ilc_continuationParameter::Pr) { + Real Rayleigh = 1 / (ilcflags_.nu * ilcflags_.kappa); + ilcflags_.nu = sqrt(mu / Rayleigh); + ilcflags_.kappa = 1.0 / sqrt(mu * Rayleigh); + } else if (ilc_cPar_ == ilc_continuationParameter::gx) { + ilcflags_.gammax = mu; + } else if (ilc_cPar_ == ilc_continuationParameter::gz) { + ilcflags_.gammaz = mu; + } else if (ilc_cPar_ == ilc_continuationParameter::gxEps) { + Real gx = ilcflags_.gammax; + Real Ra_old = 1 / (ilcflags_.nu * ilcflags_.kappa); + Real Prandtl = ilcflags_.nu / ilcflags_.kappa; + Real Rc = 1707.76; + Real Rc2 = 8053.1; + Real gc2 = 77.7567; + // get old threshold + Real Rac_old = 0; + if (gx / pi * 180.0 < gc2) + Rac_old = Rc / cos(gx); + else + Rac_old = 1.0 / 41.0 * pow(gx / pi * 180.0 - gc2, 3) + 5.0 / 14.0 * pow(gx / pi * 180.0 - gc2, 2) + + 29 * (gx / pi * 180.0 - gc2) + Rc2; + // get new threshold + Real Rac_new = 0; + if (mu / pi * 180.0 < gc2) + Rac_new = Rc / cos(mu); + else + Rac_new = 1.0 / 41.0 * pow(mu / pi * 180.0 - gc2, 3) + 5.0 / 14.0 * pow(mu / pi * 180.0 - gc2, 2) + + 29 * (mu / pi * 180.0 - gc2) + Rc2; + Real Ra_new = Ra_old / Rac_old * Rac_new; + cout << "Normalized Rayleigh number is epsilon = " << Ra_new / Rac_new - 1 << endl; + ilcflags_.nu = sqrt(Prandtl / Ra_new); + ilcflags_.kappa = 1.0 / sqrt(Prandtl * Ra_new); + ilcflags_.gammax = mu; + } else if (ilc_cPar_ == ilc_continuationParameter::Grav) { + ilcflags_.grav = mu; + } else if (ilc_cPar_ == ilc_continuationParameter::Tref) { + ilcflags_.t_ref = mu; + } else if (ilc_cPar_ == ilc_continuationParameter::P) { + ilcflags_.dPdx = mu; + } else if (ilc_cPar_ == ilc_continuationParameter::Uw) { + ilcflags_.Uwall = mu; + ilcflags_.ulowerwall = -mu * cos(ilcflags_.theta); + ilcflags_.uupperwall = mu * cos(ilcflags_.theta); + ilcflags_.wlowerwall = -mu * sin(ilcflags_.theta); + ilcflags_.wupperwall = mu * sin(ilcflags_.theta); + ; + } else if (ilc_cPar_ == ilc_continuationParameter::UwGrav) { + ilcflags_.Uwall = mu; + ilcflags_.ulowerwall = -mu * cos(ilcflags_.theta); + ilcflags_.uupperwall = mu * cos(ilcflags_.theta); + ilcflags_.wlowerwall = -mu * sin(ilcflags_.theta); + ilcflags_.wupperwall = mu * sin(ilcflags_.theta); + ; + ilcflags_.grav = 1 - mu; + } else if (ilc_cPar_ == ilc_continuationParameter::Rot) { + ilcflags_.rotation = mu; + } else if (ilc_cPar_ == ilc_continuationParameter::Theta) { + ilcflags_.theta = mu; + ilcflags_.ulowerwall = -ilcflags_.Uwall * cos(mu); + ilcflags_.uupperwall = ilcflags_.Uwall * cos(mu); + ilcflags_.wlowerwall = -ilcflags_.Uwall * sin(mu); + ilcflags_.wupperwall = ilcflags_.Uwall * sin(mu); + ; + } else if (ilc_cPar_ == ilc_continuationParameter::ThArc) { + Real xleg = Lz_ / tan(ilcflags_.theta); // hypothetical Lx + Lz_ *= sin(mu) / sin(ilcflags_.theta); // rescale Lz for new angle at const diagonal (of xleg x Lz_) + Lx_ *= Lz_ / tan(mu) / xleg; + ilcflags_.theta = mu; + ilcflags_.ulowerwall = -ilcflags_.Uwall * cos(mu); + ilcflags_.uupperwall = ilcflags_.Uwall * cos(mu); + ilcflags_.wlowerwall = -ilcflags_.Uwall * sin(mu); + ilcflags_.wupperwall = ilcflags_.Uwall * sin(mu); + ; + } else if (ilc_cPar_ == ilc_continuationParameter::ThLx) { + Lx_ *= tan(ilcflags_.theta) / tan(mu); + ilcflags_.theta = mu; + ilcflags_.ulowerwall = -ilcflags_.Uwall * cos(mu); + ilcflags_.uupperwall = ilcflags_.Uwall * cos(mu); + ilcflags_.wlowerwall = -ilcflags_.Uwall * sin(mu); + ilcflags_.wupperwall = ilcflags_.Uwall * sin(mu); + ; + } else if (ilc_cPar_ == ilc_continuationParameter::ThLz) { + Lz_ *= tan(mu) / tan(ilcflags_.theta); + ilcflags_.theta = mu; + ilcflags_.ulowerwall = -ilcflags_.Uwall * cos(mu); + ilcflags_.uupperwall = ilcflags_.Uwall * cos(mu); + ilcflags_.wlowerwall = -ilcflags_.Uwall * sin(mu); + ilcflags_.wupperwall = ilcflags_.Uwall * sin(mu); + ; + } else if (ilc_cPar_ == ilc_continuationParameter::Lx) { + Lx_ = mu; + } else if (ilc_cPar_ == ilc_continuationParameter::Lz) { + Lz_ = mu; + } else if (ilc_cPar_ == ilc_continuationParameter::Aspect) { + Real aspect = Lx_ / Lz_; + Real update = mu / aspect; + + // do only half the adjustment in x, the other in z (i.e. equivalent to Lx_new = 0.5Lx +0.5Lx * update) + Lx_ *= 1 + (update - 1) / 2.0; + Lz_ = Lx_ / mu; + } else if (ilc_cPar_ == ilc_continuationParameter::Diag) { + Real aspect = Lx_ / Lz_; + Real theta = atan(aspect); + Real diagonal = sqrt(Lx_ * Lx_ + Lz_ * Lz_); + Real update = mu - diagonal; + // Lx_ += sqrt ( (update*update * aspect*aspect) / (1 + aspect*aspect)); + Lx_ += update * sin(theta); + Lz_ = Lx_ / aspect; + } else if (ilc_cPar_ == ilc_continuationParameter::Vs) { + ilcflags_.Vsuck = mu; + } else if (ilc_cPar_ == ilc_continuationParameter::VsNu) { + ilcflags_.nu = mu; + ilcflags_.Vsuck = mu; + } else if (ilc_cPar_ == ilc_continuationParameter::VsH) { + Real nu = ilcflags_.nu; + Real Vs = nu * (1 - exp(-mu)); + Real H = nu * mu / Vs; + ya_ = 0; + yb_ = H; + ilcflags_.Vsuck = Vs; + } else if (ilc_cPar_ == ilc_continuationParameter::H) { + ya_ = 0; + yb_ = mu; + } else { + throw invalid_argument("ilcDSI::updateMu(): continuation parameter is unknown"); + } +} + +void ilcDSI::chooseMuILC(string muName) { + ilc_continuationParameter ilc_cPar = s2ilc_cPar(muName); + + if (ilc_cPar == ilc_continuationParameter::none) + ilcDSI::chooseMu(muName); + else + chooseMuILC(ilc_cPar); +} + +void ilcDSI::chooseMuILC(ilc_continuationParameter mu) { + ilc_cPar_ = mu; + Real Rayleigh; + Real Prandtl; + switch (mu) { + case ilc_continuationParameter::Ra: + Rayleigh = 1 / (ilcflags_.nu * ilcflags_.kappa); + updateMu(Rayleigh); + break; + case ilc_continuationParameter::Pr: + Prandtl = ilcflags_.nu / ilcflags_.kappa; + updateMu(Prandtl); + break; + case ilc_continuationParameter::gx: + updateMu(ilcflags_.gammax); + break; + case ilc_continuationParameter::gz: + updateMu(ilcflags_.gammaz); + break; + case ilc_continuationParameter::gxEps: + updateMu(ilcflags_.gammax); + break; + case ilc_continuationParameter::Grav: + updateMu(ilcflags_.grav); + break; + case ilc_continuationParameter::Tref: + updateMu(ilcflags_.t_ref); + break; + case ilc_continuationParameter::P: + updateMu(ilcflags_.dPdx); + break; + case ilc_continuationParameter::Uw: + updateMu(ilcflags_.uupperwall / cos(ilcflags_.theta)); + break; + case ilc_continuationParameter::UwGrav: + updateMu(ilcflags_.uupperwall / cos(ilcflags_.theta)); + break; + case ilc_continuationParameter::Rot: + updateMu(ilcflags_.rotation); + break; + case ilc_continuationParameter::Theta: + updateMu(ilcflags_.theta); + break; + case ilc_continuationParameter::ThArc: + updateMu(ilcflags_.theta); + break; + case ilc_continuationParameter::ThLx: + updateMu(ilcflags_.theta); + break; + case ilc_continuationParameter::ThLz: + updateMu(ilcflags_.theta); + break; + case ilc_continuationParameter::Lx: + updateMu(Lx_); + break; + case ilc_continuationParameter::Lz: + updateMu(Lz_); + break; + case ilc_continuationParameter::Aspect: + updateMu(Lx_ / Lz_); + break; + case ilc_continuationParameter::Diag: + updateMu(sqrt(Lx_ * Lx_ + Lz_ * Lz_)); + break; + case ilc_continuationParameter::Vs: + updateMu(ilcflags_.Vsuck); + break; + case ilc_continuationParameter::VsNu: + updateMu(ilcflags_.Vsuck); + break; + case ilc_continuationParameter::VsH: + updateMu(-log(1 - ilcflags_.Vsuck / ilcflags_.nu)); + break; + case ilc_continuationParameter::H: + updateMu(yb_ - ya_); + break; + case ilc_continuationParameter::none: + throw invalid_argument( + "ilcDSI::chooseMu(): continuation parameter is none, we should not reach this point"); + default: + throw invalid_argument("ilcDSI::chooseMu(): continuation parameter is unknown"); + } +} + +ilc_continuationParameter ilcDSI::s2ilc_cPar(string muname) { + std::transform(muname.begin(), muname.end(), muname.begin(), ::tolower); // why is the string made lower case? + if (muname == "ra") + return ilc_continuationParameter::Ra; + else if (muname == "pr") + return ilc_continuationParameter::Pr; + else if (muname == "gx") + return ilc_continuationParameter::gx; + else if (muname == "gz") + return ilc_continuationParameter::gz; + else if (muname == "gxeps") + return ilc_continuationParameter::gxEps; + else if (muname == "grav") + return ilc_continuationParameter::Grav; + else if (muname == "tref") + return ilc_continuationParameter::Tref; + else if (muname == "p") + return ilc_continuationParameter::P; + else if (muname == "uw") + return ilc_continuationParameter::Uw; + else if (muname == "uwgrav") + return ilc_continuationParameter::UwGrav; + else if (muname == "rot") + return ilc_continuationParameter::Rot; + else if (muname == "theta") + return ilc_continuationParameter::Theta; + else if (muname == "tharc") + return ilc_continuationParameter::ThArc; + else if (muname == "thlx") + return ilc_continuationParameter::ThLx; + else if (muname == "thlz") + return ilc_continuationParameter::ThLz; + else if (muname == "lx") + return ilc_continuationParameter::Lx; + else if (muname == "lz") + return ilc_continuationParameter::Lz; + else if (muname == "aspect") + return ilc_continuationParameter::Aspect; + else if (muname == "diag") + return ilc_continuationParameter::Diag; + else if (muname == "vs") + return ilc_continuationParameter::Vs; + else if (muname == "vsnu") + return ilc_continuationParameter::VsNu; + else if (muname == "vsh") + return ilc_continuationParameter::VsH; + else if (muname == "h") + return ilc_continuationParameter::H; + else + // cout << "ilcDSI::s2ilc_cPar(): ilc_continuation parameter '"+muname+"' is unknown, defaults to 'none'" << + // endl; + return ilc_continuationParameter::none; +} + +string ilcDSI::printMu() { return ilc_cPar2s(ilc_cPar_); } + +string ilcDSI::ilc_cPar2s(ilc_continuationParameter ilc_cPar) { + if (ilc_cPar == ilc_continuationParameter::none) + return cfDSI::cPar2s(cPar_); + else if (ilc_cPar == ilc_continuationParameter::Ra) + return "Ra"; + else if (ilc_cPar == ilc_continuationParameter::Pr) + return "Pr"; + else if (ilc_cPar == ilc_continuationParameter::gx) + return "gx"; + else if (ilc_cPar == ilc_continuationParameter::gz) + return "gz"; + else if (ilc_cPar == ilc_continuationParameter::gxEps) + return "gxeps"; + else if (ilc_cPar == ilc_continuationParameter::Grav) + return "Grav"; + else if (ilc_cPar == ilc_continuationParameter::Tref) + return "Tref"; + else if (ilc_cPar == ilc_continuationParameter::P) + return "P"; + else if (ilc_cPar == ilc_continuationParameter::Uw) + return "Uw"; + else if (ilc_cPar == ilc_continuationParameter::UwGrav) + return "Uw(Grav)"; + else if (ilc_cPar == ilc_continuationParameter::Rot) + return "Rot"; + else if (ilc_cPar == ilc_continuationParameter::Theta) + return "Theta"; + else if (ilc_cPar == ilc_continuationParameter::ThArc) + return "Theta(DiagArc)"; + else if (ilc_cPar == ilc_continuationParameter::ThLx) + return "Theta(Lx)"; + else if (ilc_cPar == ilc_continuationParameter::ThLz) + return "Theta(Lz)"; + else if (ilc_cPar == ilc_continuationParameter::Lx) + return "Lx"; + else if (ilc_cPar == ilc_continuationParameter::Lz) + return "Lz"; + else if (ilc_cPar == ilc_continuationParameter::Aspect) + return "Aspect"; + else if (ilc_cPar == ilc_continuationParameter::Diag) + return "Diag"; + else if (ilc_cPar == ilc_continuationParameter::Vs) + return "Vs"; + else if (ilc_cPar == ilc_continuationParameter::VsNu) + return "VsNu"; + else if (ilc_cPar == ilc_continuationParameter::VsH) + return "VsH"; + else if (ilc_cPar == ilc_continuationParameter::H) + return "H"; + else + throw invalid_argument("ilcDSI::ilc_cPar2s(): continuation parameter is not convertible to string"); +} + +void ilcDSI::saveParameters(string searchdir) { + // cfDSI::saveParameters (searchdir); + ilcflags_.save(searchdir); +} + +void ilcDSI::saveEigenvec(const Eigen::VectorXd& ev, const string label, const string outdir) { + FlowField efu(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField eft(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + vector2field(ev, efu, eft); + efu *= 1.0 / L2Norm(efu); + eft *= 1.0 / L2Norm(eft); + efu.save(outdir + "efu" + label); + eft.save(outdir + "eft" + label); +} + +void ilcDSI::saveEigenvec(const Eigen::VectorXd& evA, const Eigen::VectorXd& evB, const string label1, + const string label2, const string outdir) { + FlowField efAu(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField efBu(Nx_, Ny_, Nz_, Nd_, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField efAt(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + FlowField efBt(Nx_, Ny_, Nz_, 1, Lx_, Lz_, ya_, yb_, cfmpi_); + vector2field(evA, efAu, efAt); + vector2field(evB, efBu, efBt); + Real cu = 1.0 / sqrt(L2Norm2(efAu) + L2Norm2(efBu)); + Real ct = 1.0 / sqrt(L2Norm2(efAt) + L2Norm2(efBt)); + efAu *= cu; + efBu *= cu; + efAt *= ct; + efBt *= ct; + efAu.save(outdir + "efu" + label1); + efBu.save(outdir + "efu" + label2); + efAt.save(outdir + "eft" + label1); + efBt.save(outdir + "eft" + label2); +} + +/* OUTSIDE CLASS */ + +// G(x) = G(u,sigma) = (sigma f^T(u) - u) for orbits +void G(const FlowField& u, const FlowField& temp, Real& T, PoincareCondition* h, const FieldSymmetry& sigma, + FlowField& Gu, FlowField& Gtemp, const ILCFlags& ilcflags, const TimeStep& dt, bool Tnormalize, Real Unormalize, + int& fcount, Real& CFL, ostream& os) { + f(u, temp, T, h, Gu, Gtemp, ilcflags, dt, fcount, CFL, os); + Real funorm = L2Norm3d(Gu); + Gu *= sigma; + Gu -= u; + if (sigma.sy() == -1) { + // wall-normal mirroring in velocity requires sign change in temperature + FieldSymmetry tsigma(sigma); + FieldSymmetry inv(-1); + tsigma *= inv; + Gtemp *= tsigma; + } else { + Gtemp *= sigma; + } + Gtemp -= temp; + if (Tnormalize) { + Gu *= 1.0 / T; + Gtemp *= 1.0 / T; + } + if (Unormalize != 0.0) { + Gu *= 1. / sqrt(abs(funorm * (Unormalize - funorm))); + // u should stay off zero, so normalize with u for now - temp should also stay away from zero + Gtemp *= 1. / sqrt(abs(funorm * (Unormalize - funorm))); + } +} + +void f(const FlowField& u, const FlowField& temp, Real& T, PoincareCondition* h, FlowField& f_u, FlowField& f_temp, + const ILCFlags& ilcflags_, const TimeStep& dt_, int& fcount, Real& CFL, ostream& os) { + if (!isfinite(L2Norm(u))) { + os << "error in f: u is not finite. exiting." << endl; + exit(1); + } + ILCFlags flags(ilcflags_); + flags.logstream = &os; + TimeStep dt(dt_); + vector fields = {u, temp, FlowField(u.Nx(), u.Ny(), u.Nz(), 1, u.Lx(), u.Lz(), u.a(), u.b(), u.cfmpi())}; + + // f_u = u; + // f_temp = temp; + // No Poincare section, just integration to time T + if (h == 0) { + if (T < 0) { + os << "f: negative integration time T == " << T << endl + << "returning f(u,T) == (1+abs(T))*u" << endl + << "returning f(temp,T) == (1+abs(T))*temp" << endl; + fields[0] *= 1 + abs(T); + fields[1] *= 1 + abs(T); + return; + } + // Special case #1: no time steps + if (T == 0) { + os << "f: T==0, no integration, returning u and temp" << endl; + return; + } + dt.adjust_for_T(T, false); + flags.dt = dt; + // Adjust dt for CFL if necessary + ILC ilc(fields, flags); + ilc.advance(fields, 1); + if (dt.variable()) { + dt.adjust(ilc.CFL(fields[0]), false); + ilc.reset_dt(dt); + } + // t == current time in integration + // T == total integration time + // dT == CFL-check interval + // dt == DNS time-step + // N == T/dT, n == dT/dt; + // T == N dT, dT == n dt + // t == s dT (s is loop index) + + os << "f^T: " << flush; + for (int s = 1; s <= dt.N(); ++s) { + Real t = s * dt.dT(); + CFL = ilc.CFL(fields[0]); + if (s % 10 == 0) + os << iround(t) << flush; + else if (s % 2 == 0) { + if (CFL < dt.CFLmin()) + os << '<' << flush; + else if (CFL > dt.CFLmax()) + os << '>' << flush; + else + os << '.' << flush; + } + ilc.advance(fields, dt.n()); + if (dt.variable() && dt.adjust(CFL, false)) + ilc.reset_dt(dt); + } + + } + // Poincare section computation: return Poincare crossing nearest to t=T, with Tmin < t < Tmax. + else { + cout << "Poincare sectioning not yet implemented (markd as experimental)." << endl; + exit(1); + /* // Adjust dt for CFL if necessary + DNSPoincare dns (f_u, h, flags); + if (dt.variable()) { + dns.advance (f_u, p, 1); + dt.adjust (dns.CFL()); + dns.reset_dt (dt,u); + f_u = u; + } + // Collect all Poincare crossings between Tfudgemin and Tfudgemax + // If we don't find one in that range, go as far as Tlastchance + Real dTfudge = 1.05; + Real Tfudgemin = lesser (0.90*T, T - dTfudge*dt.dT()); + Real Tfudgemax = Greater (1.02*T, T + dTfudge*dt.dT()); + Real Tlastchance = 10*T; + + vector ucross; + vector tcross; + vector scross; + int s=0; + int crosssign = 0; // look for crossings in either direction + + os << "f^t: " << flush; + + for (Real t=0; t<=Tlastchance; t += dt.dT(), ++s) { + + CFL = dns.CFL(); + + if (s % 10 == 0) os << iround (t); + else if (s % 2 == 0) { + if (CFL > dt.CFLmax()) os << '>'; + else if (CFL < dt.CFLmin()) os << '<'; + else os << '.'; + os << flush; + } + + // Collect any Poincare crossings + bool crossed = dns.advanceToSection (f_u, p, dt.n(), crosssign, Tfudgemin); + if (crossed && t >= Tfudgemin) { + ucross.push_back (dns.ucrossing()); + tcross.push_back (dns.tcrossing()); + scross.push_back (dns.scrossing()); + } + + // If we've found at least one crossing within the fudge range, stop. + // Otherwise continue trying until Tlastchance + if (ucross.size() > 0 && t >= Tfudgemax) + break; + } + + if (ucross.size() <1) { + + os << "\nError in f(u, T, f_u, flags, dt, fcount, CFL, os) :\n"; + os << "the integration did not reach the Poincare section.\n"; + os << "Returning laminar solution and a b.s. value for the crossing time.\n"; + os << "I hope you can do something useful with them." << endl; + f_u.setToZero(); + T = dns.time(); + ++fcount; + return; + } + os << " " << flush; + + // Now select the crossing that is closest to the estimated crossing time + FlowField ubest = ucross[0]; + Real Tbest = tcross[0]; + int sbest = scross[0]; + int nbest = 0; + + for (uint n=1; n +#include +#include +#include +#include +#include "cfbasics/cfvector.h" +#include "channelflow/cfdsi.h" +#include "channelflow/cfmpi.h" +#include "channelflow/chebyshev.h" +#include "channelflow/flowfield.h" +#include "channelflow/periodicfunc.h" +#include "channelflow/symmetry.h" +#include "channelflow/tausolver.h" +#include "channelflow/utilfuncs.h" +#include "modules/ilc/ilc.h" +#include "nsolver/nsolver.h" + +using namespace std; + +namespace chflow { + +enum class ilc_continuationParameter { + none, + Ra, + Pr, + gx, + gz, + gxEps, + Grav, + Tref, + P, + Uw, + UwGrav, + Rot, + Theta, + ThArc, + ThLx, + ThLz, + Lx, + Lz, + Aspect, + Diag, + Vs, + VsNu, + VsH, + H +}; + +// Real GMRESHookstep_vector (FlowField& u, FlowField& alpha, Real& T, FieldSymmetry& sigma, +// PoincareCondition* h, +// const nsolver::hookstepSearchFlags& searchflags, +// DNSFlags& dnsflags, VEDNSFlags& vednsflags, TimeStep& dt, Real& CFL, Real Unormalize); + +std::vector ilcstats(const FlowField& u, const FlowField& temp, const ILCFlags flags = ILCFlags()); + +// header for fieldstats +string ilcfieldstatsheader(const ILCFlags flags = ILCFlags()); + +// header for fieldstats with parameter t +string ilcfieldstatsheader_t(std::string muname, const ILCFlags flags = ILCFlags()); +string ilcfieldstats(const FlowField& u, const FlowField& temp, const ILCFlags flags = ILCFlags()); +string ilcfieldstats_t(const FlowField& u, const FlowField& temp, Real t, const ILCFlags flags = ILCFlags()); +FlowField totalVelocity(const FlowField& velo, const ILCFlags flags); +FlowField totalTemperature(const FlowField& temp, const ILCFlags flags); +Real buoyPowerInput(const FlowField& u, const FlowField& temp, const ILCFlags flags, bool relative = true); +Real dissipation(const FlowField& u, const ILCFlags flags, bool normalize = true, bool relative = true); +Real heatinflux(const FlowField& temp, const ILCFlags flags, bool normalize = true, bool relative = true); +Real heatcontent(const FlowField& ttot, const ILCFlags flags); +Real Nusselt_plane(const FlowField& u, const FlowField& temp, const ILCFlags flags, bool relative = true); + +class ilcDSI : public cfDSI { + public: + /** \brief default constructor */ + ilcDSI(); + virtual ~ilcDSI() {} + + /** \brief Initialize ilcDSI */ + ilcDSI(ILCFlags& ilcflags, FieldSymmetry sigma, PoincareCondition* h, TimeStep dt, bool Tsearch, bool xrelative, + bool zrelative, bool Tnormalize, Real Unormalize, const FlowField& u, const FlowField& temp, + std::ostream* os = &std::cout); + + Eigen::VectorXd eval(const Eigen::VectorXd& x) override; + Eigen::VectorXd eval(const Eigen::VectorXd& x0, const Eigen::VectorXd& x1, + bool symopt) override; // needed for multishooting + void save(const Eigen::VectorXd& x, const string filebase, const string outdir = "./", + const bool fieldsonly = false) override; + + string stats(const Eigen::VectorXd& x) override; + pair stats_minmax(const Eigen::VectorXd& x) override; + string statsHeader() override; + void phaseShift(Eigen::VectorXd& x) override; + void phaseShift(Eigen::MatrixXd& y) override; + Real extractT(const Eigen::VectorXd& x) override; + Real extractXshift(const Eigen::VectorXd& x) override; + Real extractZshift(const Eigen::VectorXd& x) override; + + void makeVectorILC(const FlowField& u, const FlowField& temp, const FieldSymmetry& sigma, const Real T, + Eigen::VectorXd& x); + void extractVectorILC(const Eigen::VectorXd& x, FlowField& u, FlowField& temp, FieldSymmetry& sigma, Real& T); + + /// \name Compute derivatives of the two FlowFields contained in this vector + Eigen::VectorXd xdiff(const Eigen::VectorXd& a) override; + Eigen::VectorXd zdiff(const Eigen::VectorXd& a) override; + Eigen::VectorXd tdiff(const Eigen::VectorXd& a, Real epsDt) override; + /// \name Handle continuation parameter + void updateMu(Real mu) override; + void chooseMuILC(std::string muName); + void chooseMuILC(ilc_continuationParameter mu); + string printMu() override; // document + void saveParameters(string searchdir) override; + ilc_continuationParameter s2ilc_cPar(std::string muName); + string ilc_cPar2s(ilc_continuationParameter cPar); + + // Save real eigenvectors + void saveEigenvec(const Eigen::VectorXd& x, const string label, const string outdir) override; + // Save complex conjugate eigenvectors pair + void saveEigenvec(const Eigen::VectorXd& x1, const Eigen::VectorXd& x2, const string label1, const string label2, + const string outdir) override; + + protected: + ILCFlags ilcflags_; + ilc_continuationParameter ilc_cPar_ = ilc_continuationParameter::none; +}; + +// G(x) = G(u,sigma) = (sigma f^T(u) - u) for orbits +void G(const FlowField& u, const FlowField& temp, Real& T, PoincareCondition* h, const FieldSymmetry& sigma, + FlowField& Gu, FlowField& Gtemp, const ILCFlags& ilcflags, const TimeStep& dt, bool Tnormalize, Real Unormalize, + int& fcount, Real& CFL, ostream& os); +void f(const FlowField& u, const FlowField& temp, Real& T, PoincareCondition* h, FlowField& f_u, FlowField& f_temp, + const ILCFlags& ilcflags_, const TimeStep& dt_, int& fcount, Real& CFL, ostream& os); + +} // namespace chflow + +#endif From 6515e489a71d588a938c268f477f819c1ebcf06c Mon Sep 17 00:00:00 2001 From: Florian Reetz Date: Thu, 7 May 2020 20:41:01 +0200 Subject: [PATCH 06/10] Added the integration test to check compatibility with previous versions. --- CMakeLists.txt | 3 + modules/ilc/tests/CMakeLists.txt | 26 +++ modules/ilc/tests/data/tfinal.nc | Bin 0 -> 37166 bytes modules/ilc/tests/data/tinit.nc | Bin 0 -> 37166 bytes modules/ilc/tests/data/ufinal.nc | Bin 0 -> 92926 bytes modules/ilc/tests/data/uinit.nc | Bin 0 -> 92926 bytes modules/ilc/tests/ilc_timeIntegrationTest.cpp | 155 ++++++++++++++++++ 7 files changed, 184 insertions(+) create mode 100644 modules/ilc/tests/CMakeLists.txt create mode 100644 modules/ilc/tests/data/tfinal.nc create mode 100644 modules/ilc/tests/data/tinit.nc create mode 100644 modules/ilc/tests/data/ufinal.nc create mode 100644 modules/ilc/tests/data/uinit.nc create mode 100644 modules/ilc/tests/ilc_timeIntegrationTest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cad2fdc4..5ab3cf12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -212,6 +212,9 @@ endif () enable_testing() add_subdirectory(tests) +if (WITH_ILC) + add_subdirectory(modules/ilc/tests) +endif () if (WITH_PYTHON) add_subdirectory(python-wrapper/tests) endif() diff --git a/modules/ilc/tests/CMakeLists.txt b/modules/ilc/tests/CMakeLists.txt new file mode 100644 index 00000000..79249907 --- /dev/null +++ b/modules/ilc/tests/CMakeLists.txt @@ -0,0 +1,26 @@ +set(ilc_TESTS ilc_timeIntegrationTest) + +file( + COPY + ${CMAKE_CURRENT_SOURCE_DIR}/data/uinit.nc ${CMAKE_CURRENT_SOURCE_DIR}/data/ufinal.nc + DESTINATION + ${CMAKE_CURRENT_BINARY_DIR}/data +) + +file( + COPY + ${CMAKE_CURRENT_SOURCE_DIR}/data/tinit.nc ${CMAKE_CURRENT_SOURCE_DIR}/data/tfinal.nc + DESTINATION + ${CMAKE_CURRENT_BINARY_DIR}/data +) + +foreach (program ${ilc_TESTS}) + install_channelflow_application(${program} OFF) + target_link_libraries(${program}_app PUBLIC ilc) +endforeach (program) + +add_serial_test(ilc_timeIntegration ilc_timeIntegrationTest) + +if (USE_MPI) + add_mpi_test(mpi_ilc_timeIntegration ilc_timeIntegrationTest) +endif () diff --git a/modules/ilc/tests/data/tfinal.nc b/modules/ilc/tests/data/tfinal.nc new file mode 100644 index 0000000000000000000000000000000000000000..9bef355df3290810b899cfe53522e34390daa0e9 GIT binary patch literal 37166 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 literal 0 HcmV?d00001 diff --git a/modules/ilc/tests/data/tinit.nc b/modules/ilc/tests/data/tinit.nc new file mode 100644 index 0000000000000000000000000000000000000000..664c63f67f14c05fe0f2c303d1344286e04ad92f GIT binary patch literal 37166 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 Uv$yBnv^vQjtp97Wi|Hf%4|SFOOaK4? literal 0 HcmV?d00001 diff --git a/modules/ilc/tests/data/ufinal.nc b/modules/ilc/tests/data/ufinal.nc new file mode 100644 index 0000000000000000000000000000000000000000..728bdacd1c0d7affbcf8b6aa4bd565204602727b GIT binary patch literal 92926 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∋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%0AO>ny#&`T2i6p9$ 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 z�Z2yhY{+aoOp=CQCA!f91ZDTbJ~2Wk4~b)PSUV__>p{q9IAyRdDdSpAo4iz}7>0 z&IS4(E986Z_k?cGqPWO?K48^~;nQ7Vb$< z7I*RdfNC08XBtN}@MXdgH8pk%A|K87u3xNzCKDpeJ)eHKm<)Ag=L%HmBVcDP6NO~4 zH9Y11&N$ZKgq}$oltT&O_;BCOS3{BsxKw3k^L{l21H1&rzQ?7bWW04%uwOcs?o2l< z*-XZv#q98f4>9=q{_I5X9xpWc{)IJJOdkSue$@y{2EuvW$S3!8VqtVvjFMKS0J?LV zzhtwOfMUxwlk5&6x97<={gB9)f4}|w!Yhh0V9MkVu~#mH1@`mD#jDal=*PXKtqwQf z|9*O-Yn6BOVBXuKyOFSV+=@YSk|e3j8B|;AHCr% z#%z9_srpknII$2R5&S9|uezR2$dfmPGM>04X@hvUe3ZNBt^~0+{KmlAB~T6{(dT7k ziF-Fb#^x_wvjmr;Q_OBJS`Q{vZ#!k&>wqNUtej8exb95MbMc7egKTd7m!OV7IPr$b zCvImf7Mcs0YeeRwZEw9c<^2kDy~b*{>vavTsE@B5rm9EP>kEU&`0CL>RK=CzDZvrR zI+v>lJ;uF^3oMx?8MyF6OlA1xO{5My_xf^hG90+GmfIIo0AH0A9d}Puz|T*)R!qsY zfL>K+_x&UMo$=!xm= zHQ85(4lzvBnKeXyn9(ocHz^tq`6)Q7U4y|#`!7Ht( zdH#xP5RuE+X{@~Q;U+P^@J6iAzz>*%Ryii7lz?S)!pNV`!(VXB8nY%@BRndTb zYm4~azkEH>emosBc1xF%-_5F1jIf>sDV$ory9 znZ+_02O_#eJ`udle^2_W4bMErt+qLBE$T$fdiPO%?P)HWF?ucKMtfn3s^Fc2zY~FS z@W4#WW(kaWNRn%M>mZ!}r}i&qGOW|S_NjP|aOygZOtp3!DA{&W`*=2hF8gO%Zrd`r zd#rOcZX^l7p{Sd2ItaxLpFb{+E<}kn`<7U_T70D2&@OFBcy7(_2;B-oWRm}8&h6WQ z|E;+M-M-Y0HQ)KDDa4y_7sdYF8&;PKgo3K(XD z-HJlyoJKUK+i;D;`Hahdt=RV{YJ!ig5tXj4i9|}*;`mMnhBwM(I2bj%6n>Jp&mGc? z-TY>QEzuUk=}GBOq2F>fsILNItUccNe`^>!NX!-5nw*-b#WctB){oRf_ zj1x@Rk;B)75l1;^Yze+HQD?xB-KYjfF=O^(QYALo|2{TxrWh$=;skzdXCh54RZGPS zg4c}K>qnOua4YlpTlS^^!oNP9GPqX_!sWXLlAhK>_kr3{J%axn4%p>nOKyPBxfbVx z&UG+fmq5KXP2_>IPyhHNR1700sYmz;Z

LZk?Q)_fbS7_w?nSOq89Hm+_N*j4r3M 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 literal 0 HcmV?d00001 diff --git a/modules/ilc/tests/ilc_timeIntegrationTest.cpp b/modules/ilc/tests/ilc_timeIntegrationTest.cpp new file mode 100644 index 00000000..e019b993 --- /dev/null +++ b/modules/ilc/tests/ilc_timeIntegrationTest.cpp @@ -0,0 +1,155 @@ +/** + * This time integration test ensures compatibility to previous versions. + * + * This file is a part of channelflow version 2.0, https://channelflow.ch . + * License is GNU GPL version 2 or later: ./LICENSE + * + * Original author: Florian Reetz + */ + +#include +#include "channelflow/flowfield.h" +#include "modules/ilc/ilc.h" +#ifdef HAVE_MPI +#include +#endif +#include "channelflow/cfmpi.h" + +using namespace std; +using namespace chflow; + +int main(int argc, char* argv[]) { + int failure = 0; + bool verbose = true; + cfMPI_Init(&argc, &argv); + { + int taskid = mpirank(); + if (taskid == 0) { + cerr << "time_integrationTest: " << flush; + if (verbose) { + cout << "\n====================================================" << endl; + cout << "time_integrationTest\n\n"; + } + } + + string purpose( + "This program loads initial conditions from the harddisk and integrates\nit for 20 time units. The " + "resulting fields are compared to the final fields \nufinal and tfinal. L2diff decides on success of " + "test."); + ArgList args(argc, argv, purpose); + int nproc0 = args.getint("-np0", "-nproc0", 0, "number of processes for transpose/parallel ffts"); + int nproc1 = args.getint("-np1", "-nproc1", 0, "number of processes for slice fft"); + bool fftwmeasure = args.getflag("-fftwmeasure", "--fftwmeasure", "use fftw_measure instead of fftw_patient"); + Real tol = args.getreal("-t", "--tolerance", 1.0e-14, "max distance allowed for the test to pass"); + string dir = "data"; + args.check(); + + if (taskid == 0 && verbose) + cout << "Creating CfMPI object..." << flush; + CfMPI* cfmpi = &CfMPI::getInstance(nproc0, nproc1); + if (taskid == 0 && verbose) + cout << "done" << endl; + + if (taskid == 0 && verbose) + cout << "Loading FlowFields..." << endl; + FlowField u(dir + "/uinit", cfmpi); + FlowField temp(dir + "/tinit", cfmpi); + if (taskid == 0 && verbose) + cout << "done" << endl; + + if (u.taskid() == 0 && verbose) { + cout << "================================================================\n"; + cout << purpose << endl << endl; + cout << "Distribution of processes is " << u.nproc0() << "x" << u.nproc1() << endl; + } + FlowField utmp(u); + if (fftwmeasure) + utmp.optimizeFFTW(FFTW_MEASURE); + else + utmp.optimizeFFTW(FFTW_PATIENT); + fftw_savewisdom(); + + // Define integration parameters + const int n = 40; // take n steps between printouts + const Real dt = 1.0 / n; // integration timestep + + // Define DNS parameters + ILCFlags flags; + flags.baseflow = + ZeroBase; // should be Laminar base for a better test, but this test was callibrated with ZeroBase + + // Run at default flags. If you change them, recreate the test files. + flags.dt = dt; + flags.verbosity = Silent; + + if (u.taskid() == 0 && verbose) + cout << "Building FlowField q..." << flush; + vector fields = { + u, temp, FlowField(u.Nx(), u.Ny(), u.Nz(), 1, u.Lx(), u.Lz(), u.a(), u.b(), u.cfmpi(), Spectral, Spectral)}; + if (u.taskid() == 0 && verbose) + cout << "done" << endl; + if (u.taskid() == 0 && verbose) + cout << "Building dns..." << flush; + ILC ilc(fields, flags); + if (u.taskid() == 0 && verbose) + cout << "done" << endl; + + Real avtime = 0; + int i = 0; + Real T = 20; + for (Real t = 0; t <= T; t += 1) { + timeval start, end; + gettimeofday(&start, 0); + Real cfl = ilc.CFL(fields[0]); + if (fields[0].taskid() == 0 && verbose) + cout << " t == " << t << endl; + if (fields[0].taskid() == 0 && verbose) + cout << " CFL == " << cfl << endl; + Real l2n = L2Norm(fields[0]); + if (fields[0].taskid() == 0 && verbose) + cout << " L2Norm(u) == " << l2n << endl; + + // Take n steps of length dt + ilc.advance(fields, n); + if (verbose) { + gettimeofday(&end, 0); + Real sec = (Real)(end.tv_sec - start.tv_sec); + Real ms = (((Real)end.tv_usec) - ((Real)start.tv_usec)); + Real timeused = sec + ms / 1000000.; + if (fields[0].taskid() == 0) + cout << "duration for this timeunit: " << timeused << endl; + if (t != 0) { + avtime += timeused; + i++; + } + if (fields[0].taskid() == 0) + cout << endl; + } + } + + FlowField uf(dir + "/ufinal", cfmpi); + FlowField tf(dir + "/tfinal", cfmpi); + Real l2d = L2Dist(uf, fields[0]) + L2Dist(tf, fields[1]); + if (l2d > tol) { + if (fields[0].taskid() == 0) { + if (verbose) + cout << endl << "Final L2Dist: " << l2d << endl; + cerr << "\t** FAIL **" << endl; + cout << "\t** FAIL **" << endl; + } + failure = 1; + } else { + if (fields[0].taskid() == 0) { + if (verbose) + cout << endl << "Final L2Dist: " << l2d << endl; + cerr << "\t pass " << endl; + cout << "\t pass " << endl; + } + } + if (fields[0].taskid() == 0 && verbose) { + cout << "Average time/timeunit: " << avtime / i << "s" << endl; + } + } + cfMPI_Finalize(); + return failure; +} From 7be2a53f1ed54fd2309e742ddf7ba9e0df5df698 Mon Sep 17 00:00:00 2001 From: Florian Reetz Date: Tue, 26 May 2020 10:12:58 +0200 Subject: [PATCH 07/10] Added the programs to study ILC --- modules/ilc/programs/CMakeLists.txt | 15 + modules/ilc/programs/ilc_addbaseflow.cpp | 65 ++ modules/ilc/programs/ilc_continuesoln.cpp | 308 +++++++ modules/ilc/programs/ilc_edgetracking.cpp | 994 +++++++++++++++++++++ modules/ilc/programs/ilc_findeigenvals.cpp | 274 ++++++ modules/ilc/programs/ilc_findsoln.cpp | 127 +++ modules/ilc/programs/ilc_simulateflow.cpp | 223 +++++ 7 files changed, 2006 insertions(+) create mode 100644 modules/ilc/programs/CMakeLists.txt create mode 100644 modules/ilc/programs/ilc_addbaseflow.cpp create mode 100644 modules/ilc/programs/ilc_continuesoln.cpp create mode 100644 modules/ilc/programs/ilc_edgetracking.cpp create mode 100644 modules/ilc/programs/ilc_findeigenvals.cpp create mode 100644 modules/ilc/programs/ilc_findsoln.cpp create mode 100644 modules/ilc/programs/ilc_simulateflow.cpp diff --git a/modules/ilc/programs/CMakeLists.txt b/modules/ilc/programs/CMakeLists.txt new file mode 100644 index 00000000..9496bb12 --- /dev/null +++ b/modules/ilc/programs/CMakeLists.txt @@ -0,0 +1,15 @@ +set( + ilc_APPS + ilc_simulateflow + ilc_findsoln + ilc_continuesoln + ilc_findeigenvals + ilc_edgetracking + ilc_addbaseflow +) + +foreach (program ${ilc_APPS}) + install_channelflow_application(${program} bin) + target_link_libraries(${program}_app PUBLIC ilc) +endforeach (program) + diff --git a/modules/ilc/programs/ilc_addbaseflow.cpp b/modules/ilc/programs/ilc_addbaseflow.cpp new file mode 100644 index 00000000..a82b48e6 --- /dev/null +++ b/modules/ilc/programs/ilc_addbaseflow.cpp @@ -0,0 +1,65 @@ +/** + * This program adds a base flow, constructed from input parameters, + * to given velocity and temperature fluctuations. + * + * This file is a part of channelflow version 2.0, https://channelflow.ch . + * License is GNU GPL version 2 or later: ./LICENSE + * + * Original author: Florian Reetz + */ + +#include +#include +#include +#include +#include + +#include "channelflow/flowfield.h" +#include "modules/ilc/ilc.h" + +using namespace std; +using namespace channelflow; + +int main(int argc, char* argv[]) { + cfMPI_Init(&argc, &argv); + { + string purpose( + "Compute and add the base flow to velocity and " + "temerature fields based on the ILCFlags"); + + ArgList args(argc, argv, purpose); + + ILCFlags flags(args); + TimeStep dt(flags); + + const string uname = args.getstr(4, "", "input field of velocity fluctuations"); + const string tname = args.getstr(3, "", "input field of temperature fluctuations"); + const string ubfname = args.getstr(2, "", "output field of total velocity"); + const string tbfname = args.getstr(1, "", "output field of total temperature"); + + // put all fields into a vector + FlowField u(uname); + FlowField temp(tname); + vector fields = {u, temp, FlowField(u.Nx(), u.Ny(), u.Nz(), 1, u.Lx(), u.Lz(), u.a(), u.b())}; + + // construct OBE object + OBE obe(fields, flags); + + // + ChebyCoeff Ubase(obe.Ubase()); + ChebyCoeff Wbase(obe.Wbase()); + ChebyCoeff Tbase(obe.Tbase()); + + // add base flow; + for (int ny = 0; ny < u.Ny(); ++ny) { + u.cmplx(0, ny, 0, 0) += Complex(Ubase(ny), 0.0); + u.cmplx(0, ny, 0, 2) += Complex(Wbase(ny), 0.0); + temp.cmplx(0, ny, 0, 0) += Complex(Tbase(ny), 0.0); + } + u.cmplx(0, 0, 0, 1) -= Complex(flags.Vsuck, 0.); + + u.save(ubfname); + temp.save(tbfname); + } + cfMPI_Finalize(); +} diff --git a/modules/ilc/programs/ilc_continuesoln.cpp b/modules/ilc/programs/ilc_continuesoln.cpp new file mode 100644 index 00000000..e7fb76c0 --- /dev/null +++ b/modules/ilc/programs/ilc_continuesoln.cpp @@ -0,0 +1,308 @@ +/** + * Continuation program, like the program for pure shear flows. + * + * This file is a part of channelflow version 2.0, https://channelflow.ch . + * License is GNU GPL version 2 or later: ./LICENSE + * + * Original author: Florian Reetz + */ + +#include +#include "cfbasics/cfbasics.h" +#include "channelflow/flowfield.h" +#include "modules/ilc/ilcdsi.h" +#include "nsolver/nsolver.h" + +using namespace std; +using namespace channelflow; + +int main(int argc, char* argv[]) { + cfMPI_Init(&argc, &argv); + { + ArgList args(argc, argv, "parametric continuation of invariant solutions in ILC"); + + nsolver::ContinuationFlags cflags(args); + cflags.save(); + + unique_ptr N; + bool Rxsearch, Rzsearch, Tsearch; + nsolver::NewtonSearchFlags searchflags(args); + searchflags.save(); + N = unique_ptr(new nsolver::NewtonAlgorithm(searchflags)); + + Rxsearch = searchflags.xrelative; + Rzsearch = searchflags.zrelative; + Tsearch = searchflags.solntype == PeriodicOrbit ? true : false; + const bool Tnormalize = Tsearch ? false : true; + + Real diagonal = 0.0; // sqrt(Lx^2 + Lz^2) + int W = 24; + const Real EPSILON = 1e-13; + + ILCFlags ilcflags(args, searchflags.laurette); + TimeStep dt(ilcflags); + ilcflags.save(); + + args.section("Program options"); + const string muname = args.getstr("-cont", "--continuation", "", + "continuation parameter, one of [Ra, Pr, gx, gz, Grav, Tref, P, Uw, UwGrav, " + "Rot, Theta, ThArc, ThLx, ThLz, Lx, Lz, Aspect, Diag, Vs, VsNu, VsH, H]"); + const string sigmastr = + args.getstr("-sigma", "--sigma", "", "file containing sigma of sigma f^T(u) - u = 0 (default == identity)"); + const Real Unormalize = args.getreal("-un", "--unormalize", 0.0, "lower bound in energy for search"); + Real Lxtarg = args.getreal("-Lxtarg", "--LxTarget", 2 * pi, "with -cont Ltarg, aim for this value of Lx"); + Real Lztarg = args.getreal("-Lztarg", "--LzTarget", pi, "with -cont Ltarg, aim for this value of Lz"); + const bool xphasehack = + args.getflag("-xphhack", "--xphasehack", + "fix x phase so that u(x,0,Lz/2,0) - mean = 0 at x=Lx/2 (HACK for u UNSYMM in x)"); + const bool zphasehack = + args.getflag("-zphhack", "--zphasehack", + "fix z phase so that d/dz _{xy}(z) = 0 at z=Lz/2 (HACK for u UNSYMM in z)"); + const bool uUbasehack = + args.getflag("-uUbhack", "--uUbasehack", "fix u/Ubase decomposition so that == 0 at walls."); + const int nproc0 = + args.getint("-np0", "--nproc0", 0, "number of MPI-processes for transpose/number of parallel ffts"); + const int nproc1 = args.getint("-np1", "--nproc1", 0, "number of MPI-processes for one fft"); + + // check if invariant solution is relative + FieldSymmetry givenSigma; + if (sigmastr.length() != 0) + givenSigma = FieldSymmetry(sigmastr); + bool relative = Rxsearch || Rzsearch || !givenSigma.isIdentity(); + + bool restart = cflags.restartMode; + + string uname(""), tname(""), restartdir[3]; + if (restart) { + bool solutionsAvail = nsolver::readContinuationInfo(restartdir, cflags); + + if (!solutionsAvail) { + restartdir[0] = args.getpath(1, "", "directory containing solution 1"); + restartdir[1] = args.getpath(2, "", "directory containing solution 2"); + restartdir[2] = args.getpath(3, "", "directory containing solution 3"); + } + + } else { + uname = + args.getstr(2, "", "velocity field of initial solution from which to start continuation"); + tname = + args.getstr(1, "", "temperature field of initial solution from which to start continuation"); + } + args.check(); + + if (muname == "") { + cerr << "Please choose --continuation with one of [Ra, Pr, gx, gz, Grav, Tref, P, Uw, UwGrav, Rot, Theta, " + "ThArc, ThLx, ThLz, Lx, Lz, Aspect, Diag, Vs, VsNu, VsH, H]" + << endl; + exit(1); + } + args.save(); + WriteProcessInfo(argc, argv); + + CfMPI* cfmpi = &CfMPI::getInstance(nproc0, nproc1); + + FlowField u[3]; + FlowField temp[3]; + FieldSymmetry sigma[3]; + cfarray mu(3); + Real T[3]; + unique_ptr dsi; + dsi = unique_ptr(new ilcDSI()); + + if (restart) { + cout << "Restarting from previous solutions. Please be aware that DNSFlags and ILCFlags " + << "from the corresponding directories will overwrite any specified command line parameters!" << endl; + for (int i = 0; i < 3; ++i) { + u[i] = FlowField(restartdir[i] + "ubest", cfmpi); + temp[i] = FlowField(restartdir[i] + "tbest", cfmpi); + if (i == 0) { + printout("Optimizing FFTW...", false); + u[i].optimizeFFTW(FFTW_PATIENT); + fftw_savewisdom(); + printout("done"); + u[i] = FlowField(restartdir[i] + "ubest", cfmpi); + } + if (relative) { + sigma[i] = FieldSymmetry(restartdir[i] + "sigmabest.asc"); + } + load(mu[i], restartdir[i] + "mu.asc"); + if (Tsearch) { + load(T[i], restartdir[i] + "Tbest.asc"); + } else { + T[i] = ilcflags.T; + } + } + cout << "Loading flags from " + restartdir[0] + "dnsflags.txt and " + restartdir[0] + + "ilcflags.txt, neglecting command line switches." + << endl; + ilcflags.load(cfmpi->taskid(), restartdir[0]); + dt = TimeStep(ilcflags); + + // Some consistency checks of the initial solutions for a continuation in restart mode + if (!relative && (sigma[0] != sigma[1] || sigma[1] != sigma[2])) + cferror("loadSolutions error : initial symmetries should be equal for non-relative searchs"); + if (muname == "Aspect") { + // Check that diagonal of u[2]'s is same as u[0]. Rescale Lx,Lz keeping aspect ratio + // constant if diagonals are different. Normally reloaded aspect-continued data will + // have constant diagonals, but do check and fix in order to restart data with some + // small fuzz in diagonal. + diagonal = pythag(u[1].Lx(), u[1].Lz()); + for (int n = 2; n >= 0; n -= 2) { + if (abs(diagonal - pythag(u[n].Lx(), u[n].Lz())) >= EPSILON) { + cout << "Diagonal of u[" << n << "] != diagonal of u[1]. Rescaling Lx,Lz...." << endl; + Real alpha_n = atan(1 / mu[n]); + u[n].rescale(diagonal * cos(alpha_n), diagonal * sin(alpha_n)); + temp[n].rescale(diagonal * cos(alpha_n), diagonal * sin(alpha_n)); + } + } + } else if (muname == "Diag") { + // Check that aspect ratio of u[2]'s is same u[0]. Rescale Lx,Lz keeping diagonal + // constant if aspect ratios differ. Normally reloaded diagonl-continued data will + // have constant aspect ratio, but do check and fix in order to restart data with some + // small fuzz in aspect ratio. + Real aspect = u[1].Lx() / u[1].Lz(); + Real alpha = atan(1.0 / (u[1].Lx() / u[1].Lz())); + for (int n = 2; n >= 0; n -= 2) { + if (abs(aspect - u[n].Lx() / u[n].Lz()) >= EPSILON) { + cout << "Aspect ratio of u[" << n << "] != aspect ratio of u[1]. Rescaling Lx,Lz...." << endl; + Real diagonal_n = pythag(u[n].Lx(), u[n].Lz()); + u[n].rescale(diagonal_n * cos(alpha), diagonal_n * sin(alpha)); + temp[n].rescale(diagonal_n * cos(alpha), diagonal_n * sin(alpha)); + } + } + } else if (muname == "Lt") { + // Check that all solutions are colinear with (u[1].Lx, u[1].Lz) and (Lxtarg,Lztarg). + // If not, rescale u[2] and u[0] Lx,Lz so that they are + Real phi = atan((Lztarg - u[1].Lz()) / (Lxtarg - u[1].Lx())); + for (int i = 2; i >= 0; i -= 2) { + if (abs(atan((Lztarg - u[i].Lz()) / (Lxtarg - u[i].Lx())) - phi) >= EPSILON) { + cout << "u[" << i + << "] is not colinear with (u[1].Lx, u[1].Lz) and (Lxtarg,Lztarg). Rescaling to fix." + << endl; + u[i].rescale(Lxtarg - mu[i] * cos(phi), Lztarg - mu[i] * sin(phi)); + temp[i].rescale(Lxtarg - mu[i] * cos(phi), Lztarg - mu[i] * sin(phi)); + } + } + } + cout << endl << "loaded the following data..." << endl; + } else { // not a restart + // Compute initial data points for extrapolation from perturbations of given solution + u[1] = FlowField(uname, cfmpi); + temp[1] = FlowField(tname, cfmpi); + project(ilcflags.symmetries, u[1], "initial value u", cout); + project(ilcflags.tempsymmetries, temp[1], "initial value temp", cout); + fixdivnoslip(u[1]); + + u[2] = u[1]; + printout("Optimizing FFTW...", false); + u[2].optimizeFFTW(FFTW_PATIENT); // Overwrites u[2] + printout("done"); + fftw_savewisdom(); + u[2] = u[1]; + temp[2] = temp[1]; + u[0] = u[1]; + temp[0] = temp[1]; + + if (sigmastr.length() != 0) + sigma[0] = sigma[1] = sigma[2] = givenSigma; + T[0] = T[1] = T[2] = ilcflags.T; + + // begin superfluous output + Real phi, Ltarget; + if (muname == "Lt") { + phi = atan((Lztarg - u[1].Lz()) / (Lxtarg - u[1].Lx())); + Ltarget = spythag(u[1].Lx(), Lxtarg, u[1].Lz(), Lztarg, phi); + } else { + Lxtarg = u[1].Lx(); + Lztarg = u[1].Lz(); + phi = 0.0; + Ltarget = 0.0; + } + cout << "Some geometrical parameters. Phi and Ltarg are used for continuation along line in 2d (Lx,Lz) " + "plane." + << endl; + cout << " phi == " << phi << endl; + cout << "Ltarg == " << Ltarget << endl; + cout << "distance == " << pythag(Lxtarg - u[1].Lx(), Lztarg - u[1].Lz()) << endl; + cout << "u[1].Lx() == " << u[1].Lx() << endl; + cout << " Lxtarg == " << Lxtarg << endl; + cout << "u[1].Lz() == " << u[1].Lz() << endl; + cout << " Lztarg == " << Lztarg << endl; + + cout << "Lxtarg - Ltarg cos(phi) == " << (Lxtarg - Ltarget * cos(phi)) << endl; + cout << "Lztarg - Ltarg sin(phi) == " << (Lztarg - Ltarget * sin(phi)) << endl; + cout << "dt_Lx == " << dt.dt() / u[1].Lx() << endl; + + cout << "set up the following initial data..." << endl; + } + cout << setw(4) << "i" << setw(W) << "T" << setw(W) << setw(W) << "L2Norm(u)" << setw(W) << "sigma" << endl; + for (int i = 2; i >= 0; --i) { + Real l2normui = L2Norm(u[i]); + cout << setw(4) << i << setw(W) << T[i] << setw(W) << l2normui << setw(W) << sigma[i] << setw(W) << endl; + } + // end superfluous output + + dsi = unique_ptr(new ilcDSI(ilcflags, sigma[0], 0, dt, Tsearch, Rxsearch, Rzsearch, Tnormalize, + Unormalize, u[0], temp[0], N->getLogstream())); + + cout << setprecision(8); + printout("Working directory == " + pwd()); + printout("Command-line args == "); + ilcflags.verbosity = Silent; + for (int i = 0; i < argc; ++i) + cout << argv[i] << ' '; + cout << endl; + cout << "sigma == " << sigma[0] << endl; + cout << "T == " << T[0] << endl; + cout << "dPdx == " << ilcflags.dPdx << endl; + cout << "dt == " << dt.dt() << endl; + cout << "ILCFlags == " << ilcflags << endl << endl; + + dsi->setPhaseShifts(xphasehack, zphasehack, uUbasehack); + dsi->chooseMuILC(muname); + if (!restart) { + mu[1] = dsi->mu(); + mu[0] = mu[1] + cflags.initialParamStep; + mu[2] = mu[1] - cflags.initialParamStep; + } + if (muname == "Aspect" || muname == "Diag") { + cout << "aspect ratio || diagonal continuation : " << endl; + cout << setprecision(15); + cout.setf(std::ios::left); + cout << setw(4) << "n" << setw(20) << muname << setw(20) << "aspect" << setw(20) << "diagonal" << endl; + for (int n = 2; n >= 0; --n) + cout << setw(4) << n << setw(20) << mu[n] << setw(20) << u[n].Lx() / u[n].Lz() << setw(20) + << pythag(u[n].Lx(), u[n].Lz()) << endl; + cout.unsetf(std::ios::left); + } + + if (muname == "Lt") { + cout << "Lx,Lz target continuation : " << endl; + cout << setprecision(15); + cout.setf(std::ios::left); + cout << setw(4) << "n" << setw(20) << muname << setw(20) << "Lx" << setw(20) << "Lz" << endl; + for (int i = 2; i >= 0; --i) + cout << setw(4) << i << setw(20) << mu[i] << setw(20) << u[i].Lx() << setw(20) << u[i].Lz() << endl; + cout.unsetf(std::ios::left); + } + + cfarray x(3); + for (int i = 0; i <= 2; ++i) { + dsi->updateMu(mu[i]); + dsi->makeVectorILC(u[i], temp[i], sigma[i], T[i], x[i]); + } + + int Nunk = x[0].rows(); + int Nunk_total = Nunk; +#ifdef HAVE_MPI + MPI_Allreduce(&Nunk, &Nunk_total, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD); +#endif + cout << Nunk_total << " unknowns" << endl; + + Real muFinal = nsolver::continuation(*dsi, *N, x, mu, cflags); + cout << "Final mu is " << muFinal << endl; + } + cfMPI_Finalize(); + + return 0; +} diff --git a/modules/ilc/programs/ilc_edgetracking.cpp b/modules/ilc/programs/ilc_edgetracking.cpp new file mode 100644 index 00000000..f14a9c62 --- /dev/null +++ b/modules/ilc/programs/ilc_edgetracking.cpp @@ -0,0 +1,994 @@ +/** + * Edgetracking program, like the program for pure shear flows. + * + * This file is a part of channelflow version 2.0, https://channelflow.ch . + * License is GNU GPL version 2 or later: ./LICENSE + * + * Original author: Florian Reetz + */ + +#include +#include +#include +#include "channelflow/dns.h" +#include "channelflow/flowfield.h" +#include "channelflow/poissonsolver.h" +#include "channelflow/utilfuncs.h" +#include "modules/ilc/ilcdsi.h" + +#include + +using namespace std; +using namespace channelflow; + +enum Attractor { NoAttractor, Laminar, Turbulent }; + +const string tab = "\t"; + +template +void push(vector& a, T b) { + int l = a.size(); + for (int i = 0; i < l - 1; ++i) { + a[i] = a[i + 1]; + } + a[l - 1] = b; +} + +class AttractorFlags { + public: + AttractorFlags(Real EcfH = 0, Real EcfL = 0, Real L2H = 0, Real L2L = 0, + // Real EnstH = 0, + // Real EnstL = 0, + bool normToHeight = 0, int tMaxDecay = 0); + + // Threshold values + Real EcfH; + Real EcfL; + Real L2H; + Real L2L; + // Real EnstH; + // Real EnstL; + bool normToHeight; + int tMaxDecay; +}; + +class EdgetrackingFlags { + public: + EdgetrackingFlags(); + Real tLastWrite; + Real epsilonAdvance; + Real epsilonBisection; + Real lambdaStep; + Real nBisectTrajectories; + Real tMinAttractor; + Real tMaxAttractor; + bool verifyLH; + bool saveMinima; + Real saveInterval; + bool saveUH; + bool saveUL; + bool keepUL; + bool checkConvergence; + bool returnFieldAtMinEcf; + string savedir; + string directoryEnergy; + string directoryBisections; + string directoryFF; + string directoryMinima; + string filenameEnergyL; + string filenameEnergyH; + string filenameTB; +}; + +class ConvergenceData { + public: + ConvergenceData(); + vector EcfMax; + vector EcfMin; + vector tEcfMin; +}; + +int EdgeStateTracking(FlowField& uL, FlowField& uH, FlowField& tempL, FlowField& tempH, + const EdgetrackingFlags& etflags, const AttractorFlags& aflags, const TimeStep& dt, + const ILCFlags& ilcflags, ostream& os); + +void CreateFiles(const EdgetrackingFlags& etflags); +Real Bisection(FlowField& uL, FlowField& uH, FlowField& tempL, FlowField& tempH, Real t0, + const EdgetrackingFlags& etflags, const AttractorFlags& aflags, const TimeStep& dt, + const ILCFlags& ilcflags, ostream& os = std::cout); // Return: lambdaH +bool AdvanceLH(FlowField& uL0, FlowField& uH0, FlowField& tempL0, FlowField& tempH0, int& t0, + const EdgetrackingFlags& etflags, const TimeStep& dt, const ILCFlags& flags, + const AttractorFlags& aflags, ostream& os, ConvergenceData& cd); +void ChooseNewLH(const FlowField& u0, const FlowField& temp0, Real& lambdaL, Real& lambdaH, const Real t, + const EdgetrackingFlags& etflags, const AttractorFlags& aflags, const TimeStep& dt, + const ILCFlags& ilcflags, ostream& os = std::cout); +void SaveForContinuation(const FlowField& uL, const FlowField& uH, const FlowField& tempL, const FlowField& tempH, + const string dirBisections, Real t); + +Attractor CalcAttractor(const FlowField& u0, const FlowField& temp0, FlowField& returnUField, FlowField& returnTField, + const AttractorFlags& aflags, const TimeStep& dt, const ILCFlags& flags, + const string energyfile, const string saveFinalFF = "", const int tMin = 0, + const int tMax = 10000, bool verbose = false, const int t0 = 0, std::ostream& os = std::cout); +Attractor CalcAttractor(const FlowField& u0, const FlowField& temp0, const AttractorFlags& aflags, const TimeStep& dt, + const ILCFlags& flags, const string energyfile, const string saveFinalFF = "", + const int tMin = 0, const int tMax = 10000, bool verbose = false, const int t0 = 0, + std::ostream& os = std::cout); +Attractor attractor(const FlowField& u, const AttractorFlags& aflags); // attractor is only defined on velocity field +Attractor attractor(const FlowField& u, int& tDecay, Real lastEcf, const AttractorFlags& aflags); +std::string a2s(Attractor a); + +/** Relative L2-Difference between two flowfields */ +Real RelL2Diff(const FlowField& u1, const FlowField& u2) { return L2Dist(u1, u2) / (L2Norm(u1) + L2Norm(u2)); } + +int main(int argc, char* argv[]) { + cfMPI_Init(&argc, &argv); + { + WriteProcessInfo(argc, argv); + ArgList args(argc, argv, "Find edge state in ILC"); + + ILCFlags ilcflags(args); + TimeStep dt(ilcflags); + ilcflags.verbosity = Silent; + + args.section("Program options"); + const bool cont = args.getflag("-c", "--continue", "continue from a previous calculation"); + + AttractorFlags aflags; + aflags.EcfH = + args.getreal("-EcfH", "--EcfHigh", 0, "threshold in cross flow energy to consider flowfield turbulent"); + aflags.EcfL = + args.getreal("-EcfL", "--EcfLow", 0, "threshold in cross flow energy to consider flowfield laminar"); + aflags.L2H = args.getreal("-L2H", "--L2High", 0, "threshold in L2Norm to consider flowfield turbulent"); + aflags.L2L = args.getreal("-L2L", "--L2Low", 0, "threshold in L2Norm to consider flowfield laminar"); + aflags.normToHeight = + args.getflag("-n2h", "--normToHeight", "norm threshold values to height of box (instead of 1)"); + aflags.tMaxDecay = + args.getint("-maxDcy", "--tMaxDecay", 0, + "Consider a flowfield as laminar if it monotonically decays for that many timeuints"); + + EdgetrackingFlags etflags; + etflags.tMaxAttractor = args.getreal("-tMaxAtt", "--tMaxAttractor", 10000, + "maximum time for waiting that flowfield reaches attractor"); + etflags.tMinAttractor = + args.getreal("-tMinAtt", "--tMinAttractor", 0, "minimum time for waiting that flowfield reaches attractor"); + etflags.epsilonAdvance = args.getreal("-epsA", "--epsilonAdvance", 1e-4, + "maximal separation of flowfields before starting new bisectioning"); + etflags.epsilonBisection = + args.getreal("-epsB", "--epsilonBisection", 1e-6, "maximal separation of flowfields after bisectioning"); + etflags.nBisectTrajectories = + args.getint("-nBis", "--nBisecTrajectories", 1, "Minimium number of trajectories for each bisection"); + const bool chooseLHfirst = args.getflag( + "-cLH", "--chooseLHfirst", + "start edgetracking by choosing uH and uL, i.e. if you are not sure to have a turbulent field"); + etflags.lambdaStep = + args.getreal("-lS", "--lambdaStep", 0.1, "adjust lambda by this value when looking for new laminar state"); + Real lambdaH = args.getreal("-lH", "--lambdaH", 1, "initial value of lambdaH (e.g. for continuation)"); + Real lambdaL = args.getreal("-lL", "--lambdaL", 0, "initial value of lambdaL (e.g. for continuation)"); + etflags.keepUL = args.getflag("-keepUL", "--keepUL", "keep UL instead of UH"); + etflags.verifyLH = + args.getflag("-vrfyLH", "--verifyLH", + "keep on iterating uH and uL after they are separated to verify they are laminar/turbulent"); + etflags.saveInterval = args.getint("-s", "--saveInterval", 0, "save flowfield(s) to disk every s time units"); + etflags.saveUL = args.getbool("-saveUL", "--saveUL", false, "save uL"); + etflags.saveUH = args.getbool("-saveUH", "--saveUH", true, "save uH"); + etflags.saveMinima = args.getbool("-saveMin", "--saveMinima", false, "save at minima of cross flow energy"); + const int nproc0 = + args.getint("-np0", "--nproc0", 0, "number of MPI-processes for transpose/number of parallel ffts"); + const int nproc1 = args.getint("-np1", "--nproc1", 0, "number of MPI-processes for one fft"); + const string logfile = args.getstr("-log", "--logfile", "stdout", "output log (filename or \"stdout\")"); + etflags.savedir = args.getstr("-sd", "--savedir", "./", "path to save directory"); + const string u_ifname = (cont) ? "" : args.getstr(2, "initial flowfield", "initial velocity flowfield"); + const string temp_ifname = (cont) ? "" : args.getstr(1, "initial flowfield", "initial temperature flowfield"); + etflags.directoryEnergy = etflags.savedir + "energy/"; + etflags.directoryBisections = etflags.savedir + "bisections/"; + etflags.directoryFF = etflags.savedir + "flowfields/"; + etflags.directoryMinima = etflags.savedir + "minima/"; + etflags.filenameEnergyL = etflags.savedir + "energyL"; + etflags.filenameEnergyH = etflags.savedir + "energyH"; + etflags.filenameTB = etflags.savedir + "tBisections"; + + args.check(); + args.save(); + ilcflags.save(etflags.savedir); + + CfMPI* cfmpi = &CfMPI::getInstance(nproc0, nproc1); + etflags.tLastWrite = 0; + + ofstream logstream; + if (logfile == "stdout" || logfile == "cout") { + ilcflags.logstream = &cout; + } else { + if (cfmpi->taskid() == 0) + logstream.open((etflags.savedir + logfile).c_str()); + ilcflags.logstream = &logstream; + } + ostream& os = (ostream&)*ilcflags.logstream; + + printout("Edge state tracking algorithm", os); + printout("Working directory == " + pwd(), os); + printout("Command-line args == ", false, os); + for (int i = 0; i < argc; ++i) + printout(string(argv[i]) + " ", false, os); + stringstream sstr; + sstr << ilcflags; + printout("\nILCFlags: " + sstr.str(), os); + + if ((ilcflags.symmetries.length() > 0) || (ilcflags.tempsymmetries.length() > 0)) { + printout("Restricting flow to invariant subspace generated by symmetries", os); + stringstream sstr2u, sstr2t; + sstr2u << "Velocity: " << ilcflags.symmetries; + sstr2t << "Temperature: " << ilcflags.tempsymmetries; + printout(sstr2u.str(), os); + printout(sstr2t.str(), os); + } + + printout("", os); // newline + + // ******** Preparations ******** + Real t = 0; + FlowField u, uL, uH; + FlowField temp, tempL, tempH; + + fftw_loadwisdom(); + if (!cont) { + CreateFiles(etflags); + u = FlowField(u_ifname, cfmpi); + temp = FlowField(temp_ifname, cfmpi); + if (temp.Nd() != 1) + cferror("Error initializing temp field: number of dimensions must be 1!"); + uH = u; // temp for calculating projection error and optimizing FFTW + if (ilcflags.symmetries.length() > 0) { + printout("Projecting Velocity FlowField to invariant subspace generated by symmetries", os); + stringstream sstr2; + sstr2 << ilcflags.symmetries; + printout(sstr2.str(), os); + u.project(ilcflags.symmetries); + printout("Projection error == " + r2s(L2Dist(u, uH))); + } + tempH = temp; // tmp for calculating projection error and optimizing FFTW + if (ilcflags.tempsymmetries.length() > 0) { + printout("Projecting Temperature FlowField to invariant subspace generated by symmetries", os); + stringstream sstr2; + sstr2 << ilcflags.tempsymmetries; + printout(sstr2.str(), os); + temp.project(ilcflags.tempsymmetries); + printout("Projection error == " + r2s(L2Dist(temp, tempH))); + } + uH.optimizeFFTW(FFTW_PATIENT); + tempH.optimizeFFTW(FFTW_PATIENT); + + fftw_savewisdom(); + if (chooseLHfirst) { + ChooseNewLH(u, temp, lambdaL, lambdaH, 0., etflags, aflags, dt, ilcflags, os); + } + uH = u; + uH *= lambdaH; + uL = u; + uL *= lambdaL; + tempH = temp; + tempH *= lambdaH; + tempL = temp; + tempL *= lambdaL; + } else { + ifstream Tlast(etflags.filenameTB); + while (Tlast >> t) + ; + ilcflags.t0 = t; + string fffileH = etflags.directoryBisections + "uH" + r2s(t); + string fffileL = etflags.directoryBisections + "uL" + r2s(t); + string tempfileH = etflags.directoryBisections + "tempH" + r2s(t); + string tempfileL = etflags.directoryBisections + "tempL" + r2s(t); + printout("Loading flowfields from " + fffileL + " and " + fffileH, os); + uH = FlowField(fffileH, cfmpi); + uH.project(ilcflags.symmetries); + uL = FlowField(fffileL, cfmpi); + uL.project(ilcflags.symmetries); + tempH = FlowField(tempfileH, cfmpi); + tempH.project(ilcflags.tempsymmetries); + tempL = FlowField(tempfileL, cfmpi); + tempL.project(ilcflags.tempsymmetries); + u = uH; + uH.optimizeFFTW(FFTW_PATIENT); + uH = u; + + // Read file energyH and try to extract last time when something was saved (it's the first number in the + // last not-empty line not beginning with #) + ifstream fin(etflags.filenameEnergyH.c_str()); + if (fin) { + string s1; + istringstream s2; + while (getline(fin, s1)) { + if (s1 != "" && s1.find("#") == string::npos) + s2.str(s1); + } + s2 >> etflags.tLastWrite; + s1 = "Found " + i2s((int)etflags.tLastWrite) + " as last time something was written to energyH"; + printout(s1, os); + } + if (etflags.tLastWrite >= ilcflags.T - 1) { + cferror("Already over time limit"); + } + } + + EdgeStateTracking(uL, uH, tempL, tempH, etflags, aflags, dt, ilcflags, os); + } + cfMPI_Finalize(); + return 0; +} + +/** +The actual edge state tracking algorithm +*/ +int EdgeStateTracking(FlowField& uL_, FlowField& uH_, FlowField& tempL_, FlowField& tempH_, + const EdgetrackingFlags& etflags, const AttractorFlags& aflags, const TimeStep& dt, + const ILCFlags& ilcflags, ostream& os) { + int t = ilcflags.t0; + FlowField u, uL(uL_), uH(uH_); + FlowField temp, tempL(tempL_), tempH(tempH_); + + printout("Starting edge tracking at t=" + i2s((int)t), os); + + // Main timestepping loop + bool finished = false; + ConvergenceData convergencedata; + while (!finished && t <= ilcflags.T) { + // Step 1: Bisectioning + printout("\nt = " + i2s((int)t) + ": Starting Bisection"); + // Write time to file + if (uL_.taskid() == 0) { + ofstream fT((etflags.filenameTB).c_str(), ios::app); + fT << t << endl; + fT.close(); + } + + Bisection(uL, uH, tempL, tempH, t, etflags, aflags, dt, ilcflags, os); + + // Step 2: Advance FlowFields + printout("t = " + i2s((int)t) + ": Advancing flowfields"); + bool finished = AdvanceLH(uL, uH, tempL, tempH, t, etflags, dt, ilcflags, aflags, os, convergencedata); + if (!finished) { + if (etflags.keepUL) { + u = uL; + temp = tempL; + } else { + u = uH; + temp = tempH; + } + + // Step 3: Choose new Flowfields + Real lambdaL = 1, lambdaH = 1; + ChooseNewLH(u, temp, lambdaL, lambdaH, t, etflags, aflags, dt, ilcflags, os); + uL = u; + uL *= lambdaL; + uH = u; + uH *= lambdaH; + tempL = temp; + tempL *= lambdaL; + tempH = temp; + tempH *= lambdaH; + } + } + + return 0; +} + +/** Create files for energy and bisection times, if not continuing. + */ +void CreateFiles(const EdgetrackingFlags& etflags) { + // Create files + int taskid = 0; +#ifdef HAVE_MPI + MPI_Comm_rank(MPI_COMM_WORLD, &taskid); +#endif + if (taskid == 0) { + ios::openmode mode = ios::out; + mkdir(etflags.directoryEnergy); + mkdir(etflags.directoryBisections); + if (etflags.saveInterval != 0) + mkdir(etflags.directoryFF); + if (etflags.saveMinima) + mkdir(etflags.directoryMinima); + ofstream fL((etflags.filenameEnergyL).c_str(), mode); + ofstream fH((etflags.filenameEnergyH).c_str(), mode); + fL << ilcfieldstatsheader_t("t") << endl; + fH << ilcfieldstatsheader_t("t") << endl; + fL.close(); + fH.close(); + + // Bisection times + ofstream fT((etflags.filenameTB).c_str(), mode); + fT.close(); + } +} + +/** Find the edge of chaos between uL and uH. + * @param uL lower flowfield, known to decay + * @param uH upper flowfield, known to become turbulent + * @param t0 time, only for filenames + */ +Real Bisection(FlowField& uL, FlowField& uH, FlowField& tempL, FlowField& tempH, Real t0, + const EdgetrackingFlags& etflags, const AttractorFlags& aflags, const TimeStep& dt, + const ILCFlags& ilcflags, ostream& os) { + Real lambdaL = 0, lambdaH = 1; + Real l2d = RelL2Diff(uL, uH); + int i = 0; + FlowField u0(uH); + + printout( + "Initial L2Diff ( =L2(uH-uL)/(L2(uL)+L2(uH)) ) is " + r2s(l2d) + " and eps is " + r2s(etflags.epsilonBisection), + os); + int nTraj = etflags.nBisectTrajectories; + + SaveForContinuation(uL, uH, tempL, tempH, etflags.directoryBisections, t0); + while (l2d > etflags.epsilonBisection) { + i++; + if (nTraj == 1) { + Real lambda = (lambdaL + lambdaH) / 2.; + FlowField uT = uH; + uT += uL; + uT *= 0.5; + FlowField tempT = tempH; + tempT += tempL; + tempT *= 0.5; + + // Calculate attractor of ulambda and update lambaL or lambdaH accordingly + string f = ""; + Attractor a = CalcAttractor(uT, tempT, aflags, dt, ilcflags, + etflags.directoryEnergy + "e_bisection_t" + i2s(int(t0)) + "_i" + i2s(i), "", + etflags.tMinAttractor, etflags.tMaxAttractor, false, t0); + if (a == Turbulent) { + lambdaH = lambda; + uH = uT; + tempH = tempT; + f = "turbulent"; + } else if (a == Laminar) { + lambdaL = lambda; + uL = uT; + tempL = tempT; + f = "laminar"; + } else { + cferror("Bisection did not converge"); + } + l2d = RelL2Diff(uL, uH); + printout( + "Bisection t=" + i2s(t0) + ", i=" + FillSpaces(i, 3) + ": Found " + f + ", new L2Diff is " + r2s(l2d), + os); + } else { + // Take several steps for each "bisection" + // i.e. u = (1-alpha) uL + alpha uH + // abort, once u is turbulent and set uL to the alpha-value _two_ steps lower + Real alphaStep = 1. / (nTraj + 1); + + Attractor a = Laminar; + int j = 0; + Real alpha = 0; + FlowField uT, tempT; + while (a == Laminar && j <= nTraj + 10) { + j++; + // uT = (1-alpha)*uL + alpha*uH + alpha = j * alphaStep; + uT = uL; + uT *= (1. - alpha) / alpha; + uT += uH; + uT *= alpha; + tempT = tempL; + tempT *= (1. - alpha) / alpha; + tempT += tempH; + tempT *= alpha; + a = CalcAttractor( + uT, tempT, aflags, dt, ilcflags, + etflags.directoryEnergy + "e_bisection_t" + i2s(int(t0)) + "_i" + i2s(i) + "_j" + i2s(j), "", + etflags.tMinAttractor, etflags.tMaxAttractor, false, t0); + string f = "laminar"; + if (a == Turbulent) + f = "turbulent"; + printout("Bisection t=" + i2s(t0) + ", i=" + FillSpaces(i, 3) + ", j=" + FillSpaces(j, 3) + + ", alpha=" + r2s(alpha) + ": Found " + f, + os); + } + if (a == Laminar) + cferror("Did not find turbulent state, aborting"); + + if (j >= 3) { + // uL = (1-alpha[j-2])*uL + alpha[j-2]*uH + Real alphaL = (j - 2) * alphaStep; + uL *= (1. - alphaL) / alphaL; + uL += uH; + uL *= alphaL; + tempL *= (1. - alphaL) / alphaL; + tempL += tempH; + tempL *= alphaL; + } + + uH = uT; // because uT is the FlowField we know to become turbulent + tempH = tempT; + + l2d = RelL2Diff(uL, uH); + printout("Bisection t=" + i2s(t0) + ", i=" + FillSpaces(i, 3) + ": lL=" + r2s(lambdaL) + + ", lH=" + r2s(lambdaH) + ", new L2diff is " + r2s(l2d) + "\n", + os); + } + SaveForContinuation(uL, uH, tempL, tempH, etflags.directoryBisections, t0); + } + + printout("Bisection converged, L2Diff = " + r2s(l2d), os); + printout(" lambdaL = " + r2s(lambdaL) + ", lambdaH = " + r2s(lambdaH), os); + printout( + " l2(uH)/l2(u0) = " + r2s(L2Norm(uH) / L2Norm(u0)) + ", l2(uL)/l2(u0) = " + r2s(L2Norm(uL) / L2Norm(u0)), + os); + return lambdaH; +} + +/** Advance uL and uH. + * Advance till L2Diff(uL,uH)/L2Norm(uH)>c_epsilon or t > c_T. Check if they ar really turbulent/laminar if verifyLH is + * active + * + * @param uL0 lower flowfield, iterated while not stateIsOnEdge + * @param uH0 higher flowfield, iterated while not stateIsOnEdge + * @param t time when advancing starts + * + * @return t, final time + */ +bool AdvanceLH(FlowField& uL0, FlowField& uH0, FlowField& tempL0, FlowField& tempH0, int& t, + const EdgetrackingFlags& etflags, const TimeStep& dt, const ILCFlags& flags, + const AttractorFlags& aflags, ostream& os, ConvergenceData& cd) { + // Create TimeStep, pressureField and DNS objects + // Real t = t0; // Actual time + int t0 = t; + + TimeStep dtL = dt; + TimeStep dtH = dt; + vector fieldsH(3); + vector fieldsL(3); + fieldsL[0] = uL0; // is uL + fieldsH[0] = uH0; // is uH + fieldsL[1] = tempL0; // is tempL + fieldsH[1] = tempH0; // is tempH + fieldsL[2] = + FlowField(uH0.Nx(), uH0.Ny(), uH0.Nz(), 1, uH0.Lx(), uH0.Lz(), uH0.a(), uH0.b(), fieldsL[0].cfmpi()); // is qL + fieldsH[2] = + FlowField(uH0.Nx(), uH0.Ny(), uH0.Nz(), 1, uH0.Lx(), uH0.Lz(), uH0.a(), uH0.b(), fieldsH[0].cfmpi()); // is qH + ILC dnsH(fieldsH, flags); + ILC dnsL(fieldsL, flags); + ChebyCoeff ubase(laminarProfile(flags, uH0.a(), uH0.b(), uH0.Ny())); + ChebyCoeff wbase(uH0.Ny(), uH0.a(), uH0.b()); + PressureSolver psolver(uH0, ubase, wbase, flags.nu, flags.Vsuck, flags.nonlinearity); + + psolver.solve(fieldsH[2], fieldsH[0]); + psolver.solve(fieldsL[2], fieldsL[0]); + + // Open Files + ofstream fL, fH; + if (uH0.taskid() == 0) { + fL.open((etflags.filenameEnergyL).c_str(), ios::app); + fH.open((etflags.filenameEnergyH).c_str(), ios::app); + } + + // Advance uH and uL + Real t2 = t; // Internal time while verifying + Real tAttractor = etflags.tMaxAttractor + t; // Maximum time to verify + bool turbulent = false, laminar = false, stateIsOnEdge = true; + Real ecf0 = 0, ecf1 = 0, ecf2 = 0; // ecf(uH, t - 0,1,2) + vector ecf; + for (int i = 0; i < 100; ++i) + ecf.push_back(0); + while ((!turbulent || !laminar) && t <= flags.T && (stateIsOnEdge || (etflags.verifyLH && t2 <= tAttractor))) { + t2++; + // Advance uH + if (!turbulent) { + dnsH.advance(fieldsH, dtH.n()); + Real cflH = dnsH.CFL(fieldsH[0]); + if (dtH.adjust(cflH, false)) { + dnsH.reset_dt(dtH); + } + } + + // Advance uL + if (!laminar) { + dnsL.advance(fieldsL, dtL.n()); + Real cflL = dnsL.CFL(fieldsL[0]); + if (dtL.adjust(cflL, false)) { + dnsL.reset_dt(dtL); + } + } + + if (stateIsOnEdge) { + Real l2d = RelL2Diff(fieldsL[0], fieldsH[0]); + if (l2d < etflags.epsilonAdvance) { + t++; + if (t > etflags.tLastWrite) { // prevent writing the same stuff twice + printout(ilcfieldstats_t(fieldsH[0], fieldsH[1], t), fH); + printout(ilcfieldstats_t(fieldsL[0], fieldsL[1], t), fL); + } + ecf2 = ecf1; + ecf1 = ecf0; + ecf0 = Ecf(fieldsH[0]); + bool ecfMinimum = (ecf1 < ecf2) && (ecf1 <= ecf0); + bool ecfMaximum = (ecf1 > ecf2) && (ecf1 >= ecf0); + + // Check whether we are converged to a (relative) equilibrium where ecf doesn't change anymore + if (etflags.checkConvergence) { + bool eq = true; + int l = ecf.size(); + ecf[t % l] = ecf0; + for (int i = 0; i < l; ++i) { + if (ecf[i] < 1e-16 || abs(ecf[i] - ecf[0]) / ecf[0] > etflags.epsilonBisection) { + eq = false; + break; + } + } + if (eq) { + printout("Ecf is constant -- converged to a (relative) equilibrium", os); + return true; + } + + // Check whether we are converged to a periodic orbit (somewhat more subtle) + // We consider a state as periodic if either 4 minima and 4 maxima in a row occur with same distance + // and same values or when maxima,minima 1,3 and 2,4 are the same and occur at the same distance + if (ecfMaximum) + push(cd.EcfMax, ecf0); + if (ecfMinimum) { + push(cd.EcfMin, ecf0); + push(cd.tEcfMin, t); + + Real eps = 1e-3; + if (abs(cd.EcfMax[0] - cd.EcfMax[2]) / ecf0 < eps && + abs(cd.EcfMax[1] - cd.EcfMax[3]) / ecf0 < eps && + abs(cd.EcfMin[0] - cd.EcfMin[2]) / ecf0 < eps && + abs(cd.EcfMin[1] - cd.EcfMin[3]) / ecf0 < eps && + abs((cd.tEcfMin[3] - cd.tEcfMin[1]) - (cd.tEcfMin[2] - cd.tEcfMin[0])) < 5) { + printout("Converged to a periodic orbit", os); + return true; + } + } + } + + // Save fields + bool saveIntervalReached = etflags.saveInterval != 0 && t % (int)etflags.saveInterval == 0; + if (saveIntervalReached) { + if (etflags.saveUH) { + fieldsH[0].save(etflags.directoryFF + "uH" + i2s((int)t)); + fieldsH[1].save(etflags.directoryFF + "tH" + i2s((int)t)); + } + if (etflags.saveUL) { + fieldsL[0].save(etflags.directoryFF + "uL" + i2s((int)t)); + fieldsL[1].save(etflags.directoryFF + "tL" + i2s((int)t)); + } + } + if (ecfMinimum && etflags.saveMinima) { + if (etflags.saveUH) { + fieldsH[0].save(etflags.directoryMinima + "uH" + i2s((int)t)); + fieldsH[1].save(etflags.directoryMinima + "tH" + i2s((int)t)); + } + if (etflags.saveUL) { + fieldsL[0].save(etflags.directoryMinima + "uL" + i2s((int)t)); + fieldsL[1].save(etflags.directoryMinima + "tL" + i2s((int)t)); + } + } + uL0 = fieldsL[0]; + uH0 = fieldsH[0]; + tempL0 = fieldsL[1]; + tempH0 = fieldsH[1]; + } else { + // The flowfields are separated too far + // Use state when this occurs for the first time for the next bisectioning + // and keep on iterating to verify they become turbulent/laminar + printout("L2(uL) " + r2s(L2Norm(fieldsL[0])), os); + printout("L2(uH) " + r2s(L2Norm(fieldsH[0])), os); + stateIsOnEdge = false; + printout("Flowfields separated by " + r2s(RelL2Diff(fieldsL[0], fieldsH[0])), os); + printout("Advanced flowfields to t = " + i2s((int)t), os); + printout("", os); // newline + } + } + + // Check whether flowfields are laminar/turbulent + if (!turbulent && attractor(fieldsH[0], aflags) == Turbulent) { + turbulent = true; + printout("uH is turbulent at t = " + i2s((int)t2), os); + if (!etflags.verifyLH) { + cferror("uH is turbulent, check thresholds"); + } + if (t2 - t0 <= 2) { + cferror("t2 - t0 = " + r2s(t2 - t0) + ", aborting\nuH is turbulent, check your thresholds!"); + } + } + if (!laminar && attractor(fieldsL[0], aflags) == Laminar) { + laminar = true; + printout("uL is laminar at t = " + i2s((int)t2), os); + if (!etflags.verifyLH) { + cferror("uL is laminar, check thresholds"); + } + if (t2 - t0 <= 2) { + cferror("t2 - t0 = " + r2s(t2 - t0) + ", aborting\nuL is laminar, check your thresholds!"); + } + } + } + + // Abort if the flowfields did not reach the expected state + if (!laminar && etflags.verifyLH) { + cferror("verifyLH: uL does not become laminar"); + } + if (!turbulent && etflags.verifyLH) { + cferror("verifyLH: uH does not become turbulent"); + } + + // Close files + if (fieldsH[0].taskid() == 0) { + fH.close(); + fL.close(); + } + + if (t == flags.T) + return true; + return false; +} + +/** Choose two flowfields such that uL becomes laminar and uH turbulent. + * @param u0 flowfield to scale up/down + * @param lambdaL return reference for lambdaL + * @param lambdaH return reference for lambdaH + */ +void ChooseNewLH(const FlowField& u0, const FlowField& temp0, Real& lambdaL, Real& lambdaH, const Real t, + const EdgetrackingFlags& etflags, const AttractorFlags& aflags, const TimeStep& dt, + const ILCFlags& ilcflags, ostream& os) { + assert(lambdaL <= lambdaH); + printout("Choosing new laminar/turbulent flowfields", os); + bool equalLambda = (lambdaH == lambdaL) ? true : false; + + FlowField u = u0; + FlowField temp = temp0; + Attractor AH = NoAttractor, AL = NoAttractor; + // Choose turbulent field + while (AH != Turbulent) { + u = u0; + u *= lambdaH; + temp = temp0; + temp *= lambdaH; + AH = CalcAttractor(u, temp, aflags, dt, ilcflags, + etflags.directoryEnergy + "e_chooseLH_t" + i2s(int(t)) + "_lambdaH" + r2s(lambdaH), "", + etflags.tMinAttractor, etflags.tMaxAttractor, false, t); + + printout("FlowField with lambda = " + r2s(lambdaH) + " is " + a2s(AH), os); + if (AH == Laminar) { + lambdaL = lambdaH; + lambdaH += etflags.lambdaStep; + AL = Laminar; + } else if (AH == NoAttractor) { + cferror("flowfield is ambigous"); + } + } + // Choose laminar field + while (AL != Laminar) { + if (equalLambda) { + lambdaL -= etflags.lambdaStep; + equalLambda = false; + } + u = u0; + u *= lambdaL; + temp = temp0; + temp *= lambdaL; + // AL = Attractor ( u, "_chooseLH_t" + i2s(int(t)) + "_lambdaL" + r2s(lambdaL) ); + AL = CalcAttractor(u, temp, aflags, dt, ilcflags, + etflags.directoryEnergy + "e_chooseLH_t" + i2s(int(t)) + +"_lambdaL" + r2s(lambdaL), "", + etflags.tMinAttractor, etflags.tMaxAttractor, false, t); + + printout("FlowField with lambda = " + r2s(lambdaL) + " is " + a2s(AL), os); + if (AL == Turbulent) { + lambdaH = lambdaL; + lambdaL -= etflags.lambdaStep; + } else if (AL == NoAttractor) { + cferror("flowfield is ambigous"); + } + } + printout("Finished choosing uH, uL -- lambdaH = " + r2s(lambdaH) + ", lambdaL = " + r2s(lambdaL), os); + FlowField uL(u0); + uL *= lambdaL; + FlowField uH(u0); + uH *= lambdaH; + FlowField tempL(temp0); + tempL *= lambdaL; + FlowField tempH(temp0); + tempH *= lambdaH; + SaveForContinuation(uL, uH, tempL, tempH, etflags.directoryBisections, t); +} + +/** Save all data needed for continuation to file 'continue' + */ +void SaveForContinuation(const FlowField& uL, const FlowField& uH, const FlowField& tempL, const FlowField& tempH, + const string directoryBisections, Real t) { + string ffnameH = directoryBisections + "uH" + i2s((int)t); + string ffnameL = directoryBisections + "uL" + i2s((int)t); + uH.save(ffnameH); + uL.save(ffnameL); + + string tffnameH = directoryBisections + "tH" + i2s((int)t); + string tffnameL = directoryBisections + "tL" + i2s((int)t); + tempH.save(tffnameH); + tempL.save(tffnameL); + + fftw_savewisdom(); +} + +EdgetrackingFlags::EdgetrackingFlags() + : tLastWrite(0), + epsilonAdvance(1e-4), + epsilonBisection(1e-6), + lambdaStep(0.1), + nBisectTrajectories(1), + tMinAttractor(0), + tMaxAttractor(10000), + verifyLH(false), + saveMinima(false), + saveInterval(0), + saveUH(true), + saveUL(false), + keepUL(false), + checkConvergence(false), + returnFieldAtMinEcf(false), + savedir("./"), + directoryEnergy("energy/"), + directoryBisections("bisections/"), + directoryFF("flowfields/"), + directoryMinima("minima/"), + filenameEnergyL("energyL"), + filenameEnergyH("energyH"), + filenameTB("tBisections") {} + +ConvergenceData::ConvergenceData() { + for (int i = 0; i < 4; ++i) { + EcfMin.push_back(0); + EcfMax.push_back(0); + tEcfMin.push_back(0); + } +} + +/** Calculate the attractor of a given flowfield. + * The decicision whether a flowfield is laminar or turbulent is made based upon the parameters in the configfile. + */ +Attractor CalcAttractor(const FlowField& u0, const FlowField& temp0, const AttractorFlags& aflags, const TimeStep& dt_, + const ILCFlags& flags, const string energyfile, const string saveFinalFF, const int tMin, + const int tMax, bool verbose, const int t0, ostream& os) { + FlowField u(u0); + FlowField temp(temp0); + return CalcAttractor(u0, temp0, u, temp, aflags, dt_, flags, energyfile, saveFinalFF, tMin, tMax, verbose, t0, os); +} + +Attractor CalcAttractor(const FlowField& u0, const FlowField& temp0, FlowField& u, FlowField& temp, + const AttractorFlags& aflags, const TimeStep& dt_, const ILCFlags& flags, + const string energyfile, const string saveFinalFF, const int tMin, const int tMax, bool verbose, + const int t0, ostream& os) { + u = u0; + temp = temp0; + // Check what data to save and create files + + // Advance flowfield till it reaches an attractor or time limit + TimeStep dt = dt_; + FlowField q(u.Nx(), u.Ny(), u.Nz(), 1, u.Lx(), u.Lz(), u.a(), u.b(), u.cfmpi()); + vector fields = {u, temp, q}; + ILC dns(fields, flags); + ChebyCoeff ubase = laminarProfile(flags, u.a(), u.b(), u.Ny()); + ChebyCoeff wbase = ChebyCoeff(u.Ny(), u.a(), u.b()); + PressureSolver psolver(u, ubase, wbase, flags.nu, flags.Vsuck, flags.nonlinearity); + psolver.solve(fields[2], fields[0]); + ofstream f; + bool saveEnergy = (energyfile != ""); + if (saveEnergy) { + if (u0.taskid() == 0) + f.open(energyfile.c_str(), ios::out); + printout(ilcfieldstatsheader_t("t"), f); + printout(ilcfieldstats_t(u, temp, t0), f); + } + Attractor result = NoAttractor; + int t = 0; + int tDecay = 0; // timeunits that flowfield is monotonically decaying + while (result == NoAttractor && t < tMax) { + t++; + Real lastEcf = Ecf(fields[0]); // for calculating decay time + dns.advance(fields, dt.n()); + + Real cfl = dns.CFL(fields[0]); + if (dt.adjust(cfl, verbose, os)) { + dns.reset_dt(dt); + if (verbose) + printout("Adjusting dt to " + r2s(dt.dt()) + ", CFL is " + r2s(cfl), os); + } + + if (saveEnergy) { + printout(ilcfieldstats_t(fields[0], fields[1], t + t0), f); + } + + // Check if FF is turbulent or laminar + if (t > tMin) { + result = attractor(fields[0], tDecay, lastEcf, aflags); + } + } + // printout("", os); + if (saveFinalFF != "") + fields[0].save(saveFinalFF.c_str()); + if (saveEnergy && fields[0].taskid() == 0) + f.close(); + return result; +} + +AttractorFlags::AttractorFlags(Real EcfH_, Real EcfL_, Real L2H_, Real L2L_, bool n2h, int tMaxDecay_) + : EcfH(EcfH_), + EcfL(EcfL_), + L2H(L2H_), + L2L(L2L_), + // EnstH( EnstH_ ), + // EnstL( EnstL_ ), + normToHeight(n2h), + tMaxDecay(tMaxDecay_) {} + +Attractor attractor(const FlowField& u, const AttractorFlags& a) { + int t = 0; + return attractor(u, t, 0., a); +} + +Attractor attractor(const FlowField& u, int& tDecay, Real lastEcf, const AttractorFlags& a) { + const bool useEcf = (a.EcfH > 1e-12 && a.EcfL > 1e-12); + // const bool useEnst = ( a.EnstH > 1e-12 && a.EnstL > 1e-12 ); + const bool useL2 = (a.L2H > 1e-12 && a.L2L > 1e-12); + + if (not(useEcf || useL2)) { + cferror("No valid attractor settings specified\n"); + } + + // A FlowField has reached an attractor if all conditions are true + bool isLaminar = true, isTurbulent = true; + Real val = 0; + if (useEcf) { + val = Ecf(u); + if (a.normToHeight) + val /= (u.b() - u.a()); + if (val < a.EcfH) + isTurbulent = false; + if (val > a.EcfL) + isLaminar = false; + } + if (useL2) { + val = L2Norm(u); + if (a.normToHeight) + val /= sqrt((u.b() - u.a())); + if (val < a.L2H) + isTurbulent = false; + if (val > a.L2L) + isLaminar = false; + } + // if ( useEnst ) { + // val = Enstrophy(u); + // if ( a.normToHeight ) val /= sqrt((u.b() - u.a())); + // if ( val < a.EnstH ) + // isTurbulent = false; + // if ( val > a.EnstL ) + // isLaminar = false; + // } + + if (a.tMaxDecay > 0) { + if (Ecf(u) <= lastEcf) + tDecay++; + else + tDecay = 0; + + if (tDecay > a.tMaxDecay) + isLaminar = true; + } + + if (isTurbulent && isLaminar) { + cferror("FlowField is both laminar and turbulent, exiting"); + } else if (isTurbulent) { + return Turbulent; + } else if (isLaminar) { + return Laminar; + } + return NoAttractor; +} + +string a2s(Attractor a) { + if (a == Turbulent) + return "turbulent"; + if (a == Laminar) + return "laminar"; + return "neither laminar nor turbulent"; +} diff --git a/modules/ilc/programs/ilc_findeigenvals.cpp b/modules/ilc/programs/ilc_findeigenvals.cpp new file mode 100644 index 00000000..e30c95c2 --- /dev/null +++ b/modules/ilc/programs/ilc_findeigenvals.cpp @@ -0,0 +1,274 @@ +/** + * Eigenvalue program, like the program for pure shear flows. + * + * This file is a part of channelflow version 2.0, https://channelflow.ch . + * License is GNU GPL version 2 or later: ./LICENSE + * + * Original author: Florian Reetz + */ + +#include +#include +#include +#include +#include +#include +#include "cfbasics/cfvector.h" +#include "channelflow/chebyshev.h" +#include "channelflow/flowfield.h" +#include "channelflow/symmetry.h" +#include "channelflow/tausolver.h" +#include "modules/ilc/ilcdsi.h" +#include "nsolver/nsolver.h" + +using namespace std; +using namespace channelflow; + +// This program calculates eigenvalues of fixed point of plane Couette flow +// using Arnoldi iteration. The ideas and algorithm are based on Divakar +// Viswanath, "Recurrent motions within plane Couette turbulence", +// J. Fluid Mech. 580 (2007), http://arxiv.org/abs/physics/0604062. + +int main(int argc, char* argv[]) { + cfMPI_Init(&argc, &argv); + { + WriteProcessInfo(argc, argv); + int taskid = 0; +#ifdef HAVE_MPI + MPI_Comm_rank(MPI_COMM_WORLD, &taskid); +#endif + + string purpose( + "compute spectrum of eigenvalues of equilibria, traveling waves, or periodic orbit using Arnoldi " + "iteration"); + ArgList args(argc, argv, purpose); + + ILCFlags ilcflags(args); + TimeStep dt(ilcflags); + ilcflags.verbosity = Silent; + + // The Eigenvals class is utilized to solve the eigenvalue problem. + // This class requires Arnoldi class. + unique_ptr E; + nsolver::EigenvalsFlags eigenflags(args); + E = unique_ptr(new nsolver::Eigenvals(eigenflags)); + + args.section("Program options"); + const bool poincare = + args.getflag("-poinc", "--poincare", "computing eigenvalues of map on I-D=0 Poincare section"); + + const string sigstr = + args.getstr("-sigma", "--sigma", "", "file containing sigma of sigma f^T(u) - u = 0 (default == identity)"); + + const int seed = args.getint("-sd", "--seed", 1, "seed for random number generator"); + const Real smooth = args.getreal("-s", "--smoothness", 0.4, "smoothness of initial perturb, 0 < s < 1"); + const Real EPS_du = + args.getreal("-edu", "--epsdu", 1e-7, "magnitude of Arnoldi perturbation to velocity AND temperature"); + const string duname = + args.getstr("-du", "--perturbu", "", "initial perturbation velocity field, random if unset"); + const string dtname = + args.getstr("-dtemp", "--perturbt", "", "initial perturbation temperature field, random if unset"); + + const int nproc0 = + args.getint("-np0", "--nproc0", 0, "number of MPI-processes for transpose/number of parallel ffts"); + const int nproc1 = args.getint("-np1", "--nproc1", 0, "number of MPI-processes for one fft"); + + const string uname = args.getstr(2, "", "filename of EQB, TW, or PO velocity solution"); + const string tname = args.getstr(1, "", "filename of EQB, TW, or PO temperature solution"); + + CfMPI* cfmpi = &CfMPI::getInstance(nproc0, nproc1); + + args.check(); + args.save("./"); + args.save(eigenflags.outdir); + + fftw_loadwisdom(); + + srand48(seed); + const Real decay = 1.0 - smooth; + + PoincareCondition* h = poincare ? new DragDissipation() : 0; + + FlowField u(uname, cfmpi); // u*, the solution of sigma f^T(u*,temp*) - u*,temp* = 0 + FlowField temp(tname, cfmpi); // temp*, the solution of sigma f^T(u*,temp*) - u*,temp* = 0 + + const int Nx = u.Nx(); + const int Ny = u.Ny(); + const int Nz = u.Nz(); + + const int kxmin = -u.kxmaxDealiased(); + const int kxmax = u.kxmaxDealiased(); + const int kzmin = 0; + const int kzmax = u.kzmaxDealiased(); + + if (taskid == 0) { + cout << setprecision(17); + cout << " Nx == " << Nx << endl; + cout << " Ny == " << Ny << endl; + cout << " Nz == " << Nz << endl; + cout << "kxmin == " << kxmin << endl; + cout << "kxmax == " << kxmax << endl; + cout << "kzmin == " << kzmin << endl; + cout << "kzmax == " << kzmax << endl; + + cout << "dt == " << dt.dt() << endl; + cout << "dtmin == " << dt.dtmin() << endl; + cout << "dtmax == " << dt.dtmax() << endl; + cout << "CFLmin == " << dt.CFLmin() << endl; + cout << "CFLmax == " << dt.CFLmax() << endl; + } + if (!poincare) + dt.adjust_for_T(ilcflags.T); + + FieldSymmetry sigma; // defaults to identity + if (sigstr.length() != 0) + sigma = FieldSymmetry(sigstr); + + if (ilcflags.symmetries.length() > 0) { + if (taskid == 0) { + cout << "Restricting flow to invariant subspace generated by symmetries" << endl; + cout << "VelocityField: " << ilcflags.symmetries << endl; + cout << "TemperatureField: " << ilcflags.tempsymmetries << endl; + } + } + + if (taskid == 0) + cout << "DNS flags = " << ilcflags << endl; + ilcflags.save(eigenflags.outdir); + + // Set up DNS operator ("A" in Arnoldi A*b terms) + if (taskid == 0) + cout << "setting up DNS and initial fields..." << endl; + ChebyTransform trans(Ny); + + const Real l2u = L2Norm(u); + const Real eps = + (l2u < EPS_du) + ? EPS_du + : EPS_du / l2u; // for choice of epsilon, see eq. (15) in reference + // C.J. Mack, P.J. Schmid/Journal of Computational Physics 229 (2010) 541-560 + FlowField Gu(u); + FlowField Gtemp(temp); + if (taskid == 0) + cout << "computing sigma f^T(u)..." << endl; + + // Construct the dynamical-systems interface object depending on the given parameters. + unique_ptr dsi; + dsi = unique_ptr( + new ilcDSI(ilcflags, sigma, h, dt, false, false, false, false, 0.0, u, temp, E->getLogstream())); + + // Check if sigma f^T(u) - u = 0 + VectorXd x; + field2vector(u, temp, x); + + VectorXd Gx = dsi->eval(x); + vector2field(Gx, Gu, Gtemp); + + if (taskid == 0) + cout << "\nCFL == " << dsi->getCFL() << endl; + + Real l2normGu = L2Norm(Gu); + if (taskid == 0) + cout << "L2Norm(u - sigma f^T(u)) = " << l2normGu << endl; + + if (l2normGu > 1e-6) + cferror("error: (u, sigma, T) is not a solution such as sigma f^T(u) - u = 0"); + + FlowField du(u); + FlowField dtemp(temp); + du.setToZero(); + dtemp.setToZero(); + // Set du = EPS_du (random unit perturbation, "b" in Arnoldi A*b terms) + if (duname.length() == 0) { + cout << "Constructing du..." << endl; + + bool meanflow_perturb = true; + du.addPerturbations(kxmax, kzmax, 1.0, decay, meanflow_perturb); + + if (ilcflags.constraint == PressureGradient) { + // Modify du so that (du/dy|a + du/dy|b) == (dw/dy|a + dw/dy|b) == 0 + // i.e. mean wall shear == 0 + Real h = (du.b() - du.a()) / 2; + if (du.taskid() == du.task_coeff(0, 0)) { + ChebyCoeff du00 = Re(du.profile(0, 0, 0)); + ChebyCoeff dw00 = Re(du.profile(0, 0, 2)); + ChebyCoeff du00y = diff(du00); + ChebyCoeff dw00y = diff(dw00); + Real duy = (du00y.eval_a() + du00y.eval_b()) / 2; + Real dwy = (dw00y.eval_a() + dw00y.eval_b()) / 2; + + cout << "Modifying du so that it doesn't change mean pressure balance..." << endl; + cout << "pre-mod : " << endl; + cout << "(duya + duyb)/2 == " << duy << endl; + cout << "(dwya + dwyb)/2 == " << dwy << endl; + du.cmplx(0, 1, 0, 0) -= + h * Complex(duy, 0); // modify coeff of 1st chebyshev function T_1(y/h) = y/h for u + du.cmplx(0, 1, 0, 2) -= + h * Complex(dwy, 0); // modify coeff of 1st chebyshev function T_1(y/h) = y/h for w + + du00 = Re(du.profile(0, 0, 0)); + dw00 = Re(du.profile(0, 0, 2)); + du00y = diff(du00); + dw00y = diff(dw00); + cout << "post-mod : " << endl; + cout << "(duya + duyb)/2 == " << (du00y.eval_a() + du00y.eval_b()) / 2 << endl; + cout << "(dwya + dwyb)/2 == " << (dw00y.eval_a() + dw00y.eval_b()) / 2 << endl; + } + } else { // (ilcflags.constraint == BulkVelocity) + // modify du to have zero mean value + if (du.taskid() == du.task_coeff(0, 0)) { + cout << "Modifying du so that it doesn't change mean flow..." << endl; + ChebyCoeff du00 = Re(du.profile(0, 0, 0)); + ChebyCoeff dw00 = Re(du.profile(0, 0, 2)); + Real umean = du00.mean(); + Real wmean = dw00.mean(); + cout << "pre-mod : " << endl; + cout << "u mean == " << du00.mean() << endl; + cout << "w mean == " << dw00.mean() << endl; + du.cmplx(0, 0, 0, 0) -= + Complex(umean, 0); // modify coeff of 0th chebyshev function T_0(y/h) = 1 for u + du.cmplx(0, 0, 0, 2) -= + Complex(wmean, 0); // modify coeff of 0th chebyshev function T_0(y/h) = 1 for w + du00 = Re(du.profile(0, 0, 0)); + dw00 = Re(du.profile(0, 0, 2)); + cout << "post-mod : " << endl; + cout << "u mean == " << du00.mean() << endl; + cout << "w mean == " << dw00.mean() << endl; + } + } + } else { + du = FlowField(duname, cfmpi); + } + if (dtname.length() == 0) { + cout << "Constructing dtemp..." << endl; + bool meanflow_perturb = true; + dtemp.addPerturbations(kxmax, kzmax, 1.0, decay, meanflow_perturb); + } else { + dtemp = FlowField(dtname, cfmpi); + } + + if (ilcflags.symmetries.length() != 0) + project(ilcflags.symmetries, du); + if (ilcflags.tempsymmetries.length() != 0) + project(ilcflags.tempsymmetries, dtemp); + + printout("L2Norm(du) = " + r2s(L2Norm(du))); + printout("rescaling du by eps_du = " + r2s(EPS_du)); + du *= EPS_du / L2Norm(du); + printout("L2Norm(du) = " + r2s(L2Norm(du))); + + printout("L2Norm(dtemp) = " + r2s(L2Norm(dtemp))); + printout("rescaling dtemp by eps_du = " + r2s(EPS_du)); + dtemp *= EPS_du / L2Norm(dtemp); + printout("L2Norm(dtemp) = " + r2s(L2Norm(dtemp))); + + VectorXd dx; + field2vector(du, dtemp, dx); + + E->solve(*dsi, x, dx, ilcflags.T, eps); + + fftw_savewisdom(); + } + cfMPI_Finalize(); +} + diff --git a/modules/ilc/programs/ilc_findsoln.cpp b/modules/ilc/programs/ilc_findsoln.cpp new file mode 100644 index 00000000..de59f8e8 --- /dev/null +++ b/modules/ilc/programs/ilc_findsoln.cpp @@ -0,0 +1,127 @@ +/** + * Newton search program, like the program for pure shear flows. + * + * This file is a part of channelflow version 2.0, https://channelflow.ch . + * License is GNU GPL version 2 or later: ./LICENSE + * + * Original author: Florian Reetz + */ + +#include "cfbasics/cfbasics.h" +#include "channelflow/flowfield.h" +#include "modules/ilc/ilcdsi.h" +#include "nsolver/nsolver.h" + +using namespace std; +using namespace channelflow; + +int main(int argc, char* argv[]) { + cfMPI_Init(&argc, &argv); + { + ArgList args(argc, argv, "find an invariant solution in ILC using Newton-Krylov-hookstep algorithm"); + + /** Choose the Newton algorithm to be used. Currently, two options are available: simple Newton without any + * trust region optimization, and Newton with Hookstep (default). For the simple Newton, you can choose either a + * full-space algorithm to solve the Newton equations (-solver "eigen") or between the two iterative algorithms + * GMRES and BiCGStab. Newton-Hookstep requires GMRES. Note that the available parameters depend on your choice + * of the algorithm. + */ + + unique_ptr N; + nsolver::NewtonSearchFlags searchflags(args); + searchflags.save(searchflags.outdir); + N = unique_ptr(new nsolver::NewtonAlgorithm(searchflags)); + + ILCFlags ilcflags(args, searchflags.laurette); + TimeStep dt(ilcflags); + + bool Rxsearch, Rzsearch, Tsearch; + Rxsearch = searchflags.xrelative; + Rzsearch = searchflags.zrelative; + Tsearch = searchflags.solntype == PeriodicOrbit ? true : false; + + const bool Tnormalize = Tsearch ? false : true; + + /** Read in remaining arguments */ + + args.section("Program options"); + const string sigmastr = + args.getstr("-sigma", "--sigma", "", "file containing sigma of sigma f^T(u) - u = 0 (default == identity)"); + const Real unormalize = args.getreal("-un", "--unormalize", 0.0, "lower bound in energy for search"); + const int nproc0 = + args.getint("-np0", "--nproc0", 0, "number of MPI-processes for transpose/number of parallel ffts"); + const int nproc1 = args.getint("-np1", "--nproc1", 0, "number of MPI-processes for one fft"); + const bool msinit = + args.getflag("-MSinit", "--MSinitials", "read different files as the initial guesses for different shoots"); + const string uname = args.getstr(2, "", "initial guess for the velocity solution"); + const string tname = args.getstr(1, "", "initial guess for the temperature solution"); + + args.check(); + args.save(); + WriteProcessInfo(argc, argv); + ilcflags.save(); + cout << ilcflags << endl; + + CfMPI* cfmpi = &CfMPI::getInstance(nproc0, nproc1); + + FlowField u(uname, cfmpi); + FlowField temp(tname, cfmpi); + + FieldSymmetry sigma; + if (sigmastr.length() != 0) + sigma = FieldSymmetry(sigmastr); + + /** Construct the dynamical-systems interface object depending on the given parameters. Current options are + * either standard (f(u) via forward time integration) or Laurette (f(u) via Laurettes method) + */ + unique_ptr dsi; + dsi = unique_ptr(new ilcDSI(ilcflags, sigma, 0, dt, Tsearch, Rxsearch, Rzsearch, Tnormalize, unormalize, + u, temp, N->getLogstream())); + + VectorXd x_singleShot; + VectorXd x; + VectorXd yvec; + MatrixXd y; + nsolver::MultishootingDSI* msDSI = N->getMultishootingDSI(); + dsi->makeVectorILC(u, temp, sigma, ilcflags.T, x_singleShot); + msDSI->setDSI(*dsi, x_singleShot.size()); + if (msinit) { + int nSh = msDSI->nShot(); + y.resize(x_singleShot.size(), nSh); + Real Tms = ilcflags.T / nSh; + vector u_ms(nSh); + vector t_ms(nSh); + u_ms[0] = u; + t_ms[0] = temp; + for (int i = 1; i < nSh; i++) { + string uname_ms = "./Multishooting/" + uname + i2s(i); + string tname_ms = "./Multishooting/" + tname + i2s(i); + FlowField ui(uname_ms, cfmpi); + FlowField ti(tname_ms, cfmpi); + u_ms[i] = ui; + t_ms[i] = ti; + } + for (int i = 0; i < nSh; i++) { + dsi->makeVectorILC(u_ms[i], t_ms[i], sigma, Tms, yvec); + y.col(i) = yvec; + } + x = msDSI->toVector(y); + } else { + x = msDSI->makeMSVector(x_singleShot); + } + + int Nunk = x.size(); + int Nunk_total = Nunk; +#ifdef HAVE_MPI + MPI_Allreduce(&Nunk, &Nunk_total, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD); +#endif + cout << Nunk_total << " unknowns" << endl; + + Real residual = 0; + N->solve(*dsi, x, residual); + } + + cfMPI_Finalize(); + + return 0; +} diff --git a/modules/ilc/programs/ilc_simulateflow.cpp b/modules/ilc/programs/ilc_simulateflow.cpp new file mode 100644 index 00000000..efd94fb9 --- /dev/null +++ b/modules/ilc/programs/ilc_simulateflow.cpp @@ -0,0 +1,223 @@ +/** + * DNS program, like the program for pure shear flows. + * + * This file is a part of channelflow version 2.0, https://channelflow.ch . + * License is GNU GPL version 2 or later: ./LICENSE + * + * Original author: Florian Reetz + */ + +#include +#include +#include +#include +#include + +#include "cfbasics/cfvector.h" +#include "cfbasics/mathdefs.h" +#include "channelflow/chebyshev.h" +#include "channelflow/dns.h" +#include "channelflow/flowfield.h" +#include "channelflow/poissonsolver.h" +#include "channelflow/symmetry.h" +#include "channelflow/tausolver.h" +#include "channelflow/utilfuncs.h" +#include "modules/ilc/ilcdsi.h" + +using namespace std; +using namespace channelflow; + +string printdiagnostics(FlowField& u, const DNS& dns, Real t, const TimeStep& dt, Real nu, Real umin, bool vardt, + bool pl2norm, bool pchnorm, bool pdissip, bool pshear, bool pdiverge, bool pUbulk, bool pubulk, + bool pdPdx, bool pcfl); + +int main(int argc, char* argv[]) { + cfMPI_Init(&argc, &argv); + { + WriteProcessInfo(argc, argv); + string purpose( + "integrate inclined layer convection (ILC) in the channel flow domain from a given " + "initial condition and save velocity and temperature fields to disk."); + + ArgList args(argc, argv, purpose); + + ILCFlags flags(args); + TimeStep dt(flags); + + args.section("Program options"); + const string outdir = args.getpath("-o", "--outdir", "data/", "output directory"); + const string ulabel = args.getstr("-ul", "--ulabel", "u", "output velocity field prefix"); + const string tlabel = args.getstr("-tl", "--tlabel", "t", "output temperature field prefix"); + const bool savep = args.getflag("-sp", "--savepressure", "save pressure fields"); + + const bool pcfl = args.getflag("-cfl", "--cfl", "print CFL number each dT"); + const bool pl2norm = args.getflag("-l2", "--l2norm", "print L2Norm(u) each dT"); + const bool pchnorm = args.getbool("-ch", "--chebyNorm", true, "print chebyNorm(u) each dT"); + const bool pdissip = args.getflag("-D", "--dissipation", "print dissipation each dT"); + const bool pshear = args.getflag("-I", "--input", "print wall shear power input each dT"); + const bool pdiverge = args.getflag("-dv", "--divergence", "print divergence each dT"); + const bool pubulk = args.getflag("-u", "--ubulk", "print ubulk each dT"); + const bool pUbulk = args.getflag("-Up", "--Ubulk-print", "print Ubulk each dT"); + const bool pdPdx = args.getflag("-p", "--pressure", "print pressure gradient each dT"); + const Real umin = args.getreal("-u", "--umin", 0.0, "stop if chebyNorm(u) < umin"); + + const Real ecfmin = args.getreal("-e", "--ecfmin", 0.0, "stop if Ecf(u) < ecfmin"); + const int saveint = args.getint("-s", "--saveinterval", 1, "save fields every s dT"); + + const int nproc0 = + args.getint("-np0", "--nproc0", 0, "number of MPI-processes for transpose/number of parallel ffts"); + const int nproc1 = args.getint("-np1", "--nproc1", 0, "number of MPI-processes for one fft"); + const string uname = args.getstr(2, "", "initial condition of velocity field"); + const string tname = args.getstr(1, "", "initial condition of temperature field"); + + args.check(); + args.save("./"); + cfbasics::mkdir(outdir); + args.save(outdir); + flags.save(outdir); + + CfMPI* cfmpi = &CfMPI::getInstance(nproc0, nproc1); + + printout("Constructing u,q, and optimizing FFTW..."); + FlowField u(uname, cfmpi); + FlowField temp(tname, cfmpi); + + const int Nx = u.Nx(); + const int Ny = u.Ny(); + const int Nz = u.Nz(); + const Real Lx = u.Lx(); + const Real Lz = u.Lz(); + const Real a = u.a(); + const Real b = u.b(); + + FlowField q(Nx, Ny, Nz, 1, Lx, Lz, a, b, cfmpi); + const bool inttime = + (abs(saveint * dt.dT() - int(saveint * dt.dT())) < 1e-12) && (abs(flags.t0 - int(flags.t0)) < 1e-12) + ? true + : false; + + cout << "Uwall == " << flags.uupperwall << endl; + cout << "Wwall == " << flags.wupperwall << endl; + cout << "DeltaT == " << flags.tlowerwall - flags.tupperwall << endl; + cout << "ilcflags == " << flags << endl; + cout << "constructing ILC DNS..." << endl; + ILC dns({u, temp, q}, flags); + // u.setnu (flags.nu); + + dns.Ubase().save(outdir + "Ubase"); + dns.Wbase().save(outdir + "Wbase"); + dns.Tbase().save(outdir + "Tbase"); + + // ChebyCoeff Ubase = laminarProfile (flags, u.a(), u.b(), u.Ny()); + // Ubase.save("Ubase2"); + + PressureSolver psolver(u, dns.Ubase(), dns.Wbase(), flags.nu, flags.Vsuck, + flags.nonlinearity); // NOT CORRECT FOR ILC + psolver.solve(q, u); + vector fields = {u, temp, q}; + + ios::openmode openflag = (flags.t0 > 0) ? ios::app : ios::out; + + ofstream eout, x0out; + openfile(eout, outdir + "energy.asc", openflag); + eout << ilcfieldstatsheader_t("t", flags) << endl; + + FlowField u0, du, tmp; + + int i = 0; + for (Real t = flags.t0; t <= flags.T; t += dt.dT()) { + string s; + s = printdiagnostics(fields[0], dns, t, dt, flags.nu, umin, dt.variable(), pl2norm, pchnorm, pdissip, + pshear, pdiverge, pUbulk, pubulk, pdPdx, pcfl); + if (ecfmin > 0 && Ecf(fields[0]) < ecfmin) { + cferror("Ecf < ecfmin == " + cfbasics::r2s(ecfmin) + ", exiting"); + } + + cout << s; + s = ilcfieldstats_t(fields[0], fields[1], t, flags); + eout << s << endl; + + if (saveint != 0 && i % saveint == 0) { + fields[0].save(outdir + ulabel + t2s(t, inttime)); + fields[1].save(outdir + tlabel + t2s(t, inttime)); + if (savep) + fields[1].save(outdir + "p" + t2s(t, inttime)); + } + i++; + + dns.advance(fields, dt.n()); + + if (dt.variable() && + dt.adjust(dns.CFL(fields[0]))) // TODO: dt.variable()==true is checked twice here, remove it. + dns.reset_dt(dt); + } + cout << "done!" << endl; + } + cfMPI_Finalize(); +} + +string printdiagnostics(FlowField& u, const DNS& dns, Real t, const TimeStep& dt, Real nu, Real umin, bool vardt, + bool pl2norm, bool pchnorm, bool pdissip, bool pshear, bool pdiverge, bool pUbulk, bool pubulk, + bool pdPdx, bool pcfl) { + // Printing diagnostics + stringstream sout; + sout << " t == " << t << endl; + if (vardt) + sout << " dt == " << Real(dt) << endl; + if (pl2norm) + sout << " L2Norm(u) == " << L2Norm(u) << endl; + + if (pchnorm || umin != 0.0) { + Real chnorm = chebyNorm(u); + sout << "chebyNorm(u) == " << chnorm << endl; + if (chnorm < umin) { + cout << "Exiting: chebyNorm(u) < umin." << endl; + exit(0); + } + } + Real h = 0.5 * (u.b() - u.a()); + u += dns.Ubase(); + if (pl2norm) + sout << " energy(u+U) == " << 0.5 * L2Norm(u) << endl; + if (pdissip) + sout << " dissip(u+U) == " << dissipation(u) << endl; + if (pshear) + sout << "wallshear(u+U) == " << abs(wallshearLower(u)) + abs(wallshearUpper(u)) << endl; + if (pdiverge) + sout << " divNorm(u+U) == " << divNorm(u) << endl; + if (pUbulk) + sout << "mean u+U Ubulk == " << dns.Ubulk() << endl; + u -= dns.Ubase(); + if (u.taskid() == u.task_coeff(0, 0)) { + if (pubulk) + sout << " ubulk == " << Re(u.profile(0, 0, 0)).mean() << endl; + } + if (pdPdx) + sout << " dPdx == " << dns.dPdx() << endl; + if (pl2norm) + sout << " L2Norm(u) == " << L2Norm(u) << endl; + if (pl2norm) + sout << " L2Norm3d(u) == " << L2Norm3d(u) << endl; + + Real cfl = dns.CFL(u); + if (u.taskid() == u.task_coeff(0, 0)) { + ChebyCoeff U = dns.Ubase(); + ChebyCoeff W = dns.Wbase(); + + U.makeSpectral(); + U += Re(u.profile(0, 0, 0)); + Real Ucenter = U.eval(0.5 * (u.a() + u.b())); + Real Uwall = cfbasics::pythag(0.5 * (U.eval_b() - U.eval_a()), 0.5 * (W.eval_b() - W.eval_a())); + Real Umean = U.mean(); + sout << " 1/nu == " << 1 / nu << endl; + sout << " Uwall h/nu == " << Uwall * h / nu << endl; + sout << " Ubulk h/nu == " << dns.Ubulk() * h / nu << endl; + sout << " Umean h/nu == " << Umean << " * " << h << " / " << nu << endl; + sout << " Umean h/nu == " << Umean * h / nu << endl; + sout << " Uparab h/nu == " << 1.5 * dns.Ubulk() * h / nu << endl; + sout << "Ucenter h/nu == " << Ucenter * h / nu << endl; + } + sout << " CFL == " << cfl << endl; + return sout.str(); +} + From 980b1ed53cbbcb3e040b9868fe1cc60ac1e6dc00 Mon Sep 17 00:00:00 2001 From: Florian Reetz Date: Tue, 26 May 2020 10:29:09 +0200 Subject: [PATCH 08/10] changed the namespaces, e.g. channelflow to chflow --- CMakeLists.txt | 1 + modules/ilc/programs/ilc_addbaseflow.cpp | 2 +- modules/ilc/programs/ilc_continuesoln.cpp | 16 ++++++++-------- modules/ilc/programs/ilc_edgetracking.cpp | 2 +- modules/ilc/programs/ilc_findeigenvals.cpp | 14 +++++++------- modules/ilc/programs/ilc_findsoln.cpp | 18 +++++++++--------- modules/ilc/programs/ilc_simulateflow.cpp | 8 ++++---- 7 files changed, 31 insertions(+), 30 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ab3cf12..e9d5d052 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -196,6 +196,7 @@ if (WITH_ILC) message(FATAL_ERROR "Compiling the ILC module requires -DWITH_NSOLVER=ON") endif() add_subdirectory(modules/ilc) + add_subdirectory(modules/ilc/programs) endif () # Check if we want to build the python wrapper and have boost-python diff --git a/modules/ilc/programs/ilc_addbaseflow.cpp b/modules/ilc/programs/ilc_addbaseflow.cpp index a82b48e6..70fb278b 100644 --- a/modules/ilc/programs/ilc_addbaseflow.cpp +++ b/modules/ilc/programs/ilc_addbaseflow.cpp @@ -18,7 +18,7 @@ #include "modules/ilc/ilc.h" using namespace std; -using namespace channelflow; +using namespace chflow; int main(int argc, char* argv[]) { cfMPI_Init(&argc, &argv); diff --git a/modules/ilc/programs/ilc_continuesoln.cpp b/modules/ilc/programs/ilc_continuesoln.cpp index e7fb76c0..b554ede5 100644 --- a/modules/ilc/programs/ilc_continuesoln.cpp +++ b/modules/ilc/programs/ilc_continuesoln.cpp @@ -14,21 +14,21 @@ #include "nsolver/nsolver.h" using namespace std; -using namespace channelflow; +using namespace chflow; int main(int argc, char* argv[]) { cfMPI_Init(&argc, &argv); { ArgList args(argc, argv, "parametric continuation of invariant solutions in ILC"); - nsolver::ContinuationFlags cflags(args); + ContinuationFlags cflags(args); cflags.save(); - unique_ptr N; + unique_ptr N; bool Rxsearch, Rzsearch, Tsearch; - nsolver::NewtonSearchFlags searchflags(args); + NewtonSearchFlags searchflags(args); searchflags.save(); - N = unique_ptr(new nsolver::NewtonAlgorithm(searchflags)); + N = unique_ptr(new NewtonAlgorithm(searchflags)); Rxsearch = searchflags.xrelative; Rzsearch = searchflags.zrelative; @@ -74,7 +74,7 @@ int main(int argc, char* argv[]) { string uname(""), tname(""), restartdir[3]; if (restart) { - bool solutionsAvail = nsolver::readContinuationInfo(restartdir, cflags); + bool solutionsAvail = readContinuationInfo(restartdir, cflags); if (!solutionsAvail) { restartdir[0] = args.getpath(1, "", "directory containing solution 1"); @@ -286,7 +286,7 @@ int main(int argc, char* argv[]) { cout.unsetf(std::ios::left); } - cfarray x(3); + cfarray x(3); for (int i = 0; i <= 2; ++i) { dsi->updateMu(mu[i]); dsi->makeVectorILC(u[i], temp[i], sigma[i], T[i], x[i]); @@ -299,7 +299,7 @@ int main(int argc, char* argv[]) { #endif cout << Nunk_total << " unknowns" << endl; - Real muFinal = nsolver::continuation(*dsi, *N, x, mu, cflags); + Real muFinal = continuation(*dsi, *N, x, mu, cflags); cout << "Final mu is " << muFinal << endl; } cfMPI_Finalize(); diff --git a/modules/ilc/programs/ilc_edgetracking.cpp b/modules/ilc/programs/ilc_edgetracking.cpp index f14a9c62..976d7a04 100644 --- a/modules/ilc/programs/ilc_edgetracking.cpp +++ b/modules/ilc/programs/ilc_edgetracking.cpp @@ -19,7 +19,7 @@ #include using namespace std; -using namespace channelflow; +using namespace chflow; enum Attractor { NoAttractor, Laminar, Turbulent }; diff --git a/modules/ilc/programs/ilc_findeigenvals.cpp b/modules/ilc/programs/ilc_findeigenvals.cpp index e30c95c2..0c6d03b4 100644 --- a/modules/ilc/programs/ilc_findeigenvals.cpp +++ b/modules/ilc/programs/ilc_findeigenvals.cpp @@ -22,7 +22,7 @@ #include "nsolver/nsolver.h" using namespace std; -using namespace channelflow; +using namespace chflow; // This program calculates eigenvalues of fixed point of plane Couette flow // using Arnoldi iteration. The ideas and algorithm are based on Divakar @@ -49,9 +49,9 @@ int main(int argc, char* argv[]) { // The Eigenvals class is utilized to solve the eigenvalue problem. // This class requires Arnoldi class. - unique_ptr E; - nsolver::EigenvalsFlags eigenflags(args); - E = unique_ptr(new nsolver::Eigenvals(eigenflags)); + unique_ptr E; + EigenvalsFlags eigenflags(args); + E = unique_ptr(new Eigenvals(eigenflags)); args.section("Program options"); const bool poincare = @@ -158,10 +158,10 @@ int main(int argc, char* argv[]) { new ilcDSI(ilcflags, sigma, h, dt, false, false, false, false, 0.0, u, temp, E->getLogstream())); // Check if sigma f^T(u) - u = 0 - VectorXd x; + Eigen::VectorXd x; field2vector(u, temp, x); - VectorXd Gx = dsi->eval(x); + Eigen::VectorXd Gx = dsi->eval(x); vector2field(Gx, Gu, Gtemp); if (taskid == 0) @@ -262,7 +262,7 @@ int main(int argc, char* argv[]) { dtemp *= EPS_du / L2Norm(dtemp); printout("L2Norm(dtemp) = " + r2s(L2Norm(dtemp))); - VectorXd dx; + Eigen::VectorXd dx; field2vector(du, dtemp, dx); E->solve(*dsi, x, dx, ilcflags.T, eps); diff --git a/modules/ilc/programs/ilc_findsoln.cpp b/modules/ilc/programs/ilc_findsoln.cpp index de59f8e8..4e7fd02b 100644 --- a/modules/ilc/programs/ilc_findsoln.cpp +++ b/modules/ilc/programs/ilc_findsoln.cpp @@ -13,7 +13,7 @@ #include "nsolver/nsolver.h" using namespace std; -using namespace channelflow; +using namespace chflow; int main(int argc, char* argv[]) { cfMPI_Init(&argc, &argv); @@ -27,10 +27,10 @@ int main(int argc, char* argv[]) { * of the algorithm. */ - unique_ptr N; - nsolver::NewtonSearchFlags searchflags(args); + unique_ptr N; + NewtonSearchFlags searchflags(args); searchflags.save(searchflags.outdir); - N = unique_ptr(new nsolver::NewtonAlgorithm(searchflags)); + N = unique_ptr(new NewtonAlgorithm(searchflags)); ILCFlags ilcflags(args, searchflags.laurette); TimeStep dt(ilcflags); @@ -78,11 +78,11 @@ int main(int argc, char* argv[]) { dsi = unique_ptr(new ilcDSI(ilcflags, sigma, 0, dt, Tsearch, Rxsearch, Rzsearch, Tnormalize, unormalize, u, temp, N->getLogstream())); - VectorXd x_singleShot; - VectorXd x; - VectorXd yvec; - MatrixXd y; - nsolver::MultishootingDSI* msDSI = N->getMultishootingDSI(); + Eigen::VectorXd x_singleShot; + Eigen::VectorXd x; + Eigen::VectorXd yvec; + Eigen::MatrixXd y; + MultishootingDSI* msDSI = N->getMultishootingDSI(); dsi->makeVectorILC(u, temp, sigma, ilcflags.T, x_singleShot); msDSI->setDSI(*dsi, x_singleShot.size()); if (msinit) { diff --git a/modules/ilc/programs/ilc_simulateflow.cpp b/modules/ilc/programs/ilc_simulateflow.cpp index efd94fb9..b485aaaa 100644 --- a/modules/ilc/programs/ilc_simulateflow.cpp +++ b/modules/ilc/programs/ilc_simulateflow.cpp @@ -25,7 +25,7 @@ #include "modules/ilc/ilcdsi.h" using namespace std; -using namespace channelflow; +using namespace chflow; string printdiagnostics(FlowField& u, const DNS& dns, Real t, const TimeStep& dt, Real nu, Real umin, bool vardt, bool pl2norm, bool pchnorm, bool pdissip, bool pshear, bool pdiverge, bool pUbulk, bool pubulk, @@ -72,7 +72,7 @@ int main(int argc, char* argv[]) { args.check(); args.save("./"); - cfbasics::mkdir(outdir); + mkdir(outdir); args.save(outdir); flags.save(outdir); @@ -130,7 +130,7 @@ int main(int argc, char* argv[]) { s = printdiagnostics(fields[0], dns, t, dt, flags.nu, umin, dt.variable(), pl2norm, pchnorm, pdissip, pshear, pdiverge, pUbulk, pubulk, pdPdx, pcfl); if (ecfmin > 0 && Ecf(fields[0]) < ecfmin) { - cferror("Ecf < ecfmin == " + cfbasics::r2s(ecfmin) + ", exiting"); + cferror("Ecf < ecfmin == " + r2s(ecfmin) + ", exiting"); } cout << s; @@ -207,7 +207,7 @@ string printdiagnostics(FlowField& u, const DNS& dns, Real t, const TimeStep& dt U.makeSpectral(); U += Re(u.profile(0, 0, 0)); Real Ucenter = U.eval(0.5 * (u.a() + u.b())); - Real Uwall = cfbasics::pythag(0.5 * (U.eval_b() - U.eval_a()), 0.5 * (W.eval_b() - W.eval_a())); + Real Uwall = pythag(0.5 * (U.eval_b() - U.eval_a()), 0.5 * (W.eval_b() - W.eval_a())); Real Umean = U.mean(); sout << " 1/nu == " << 1 / nu << endl; sout << " Uwall h/nu == " << Uwall * h / nu << endl; From 0f538a8d4323fdcbca2bb152bd271dfee252ea46 Mon Sep 17 00:00:00 2001 From: Florian Reetz Date: Tue, 26 May 2020 10:33:31 +0200 Subject: [PATCH 09/10] applied clang-format to the programs --- modules/ilc/programs/ilc_findeigenvals.cpp | 1 - modules/ilc/programs/ilc_simulateflow.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/modules/ilc/programs/ilc_findeigenvals.cpp b/modules/ilc/programs/ilc_findeigenvals.cpp index 0c6d03b4..d838b630 100644 --- a/modules/ilc/programs/ilc_findeigenvals.cpp +++ b/modules/ilc/programs/ilc_findeigenvals.cpp @@ -271,4 +271,3 @@ int main(int argc, char* argv[]) { } cfMPI_Finalize(); } - diff --git a/modules/ilc/programs/ilc_simulateflow.cpp b/modules/ilc/programs/ilc_simulateflow.cpp index b485aaaa..1adeb566 100644 --- a/modules/ilc/programs/ilc_simulateflow.cpp +++ b/modules/ilc/programs/ilc_simulateflow.cpp @@ -220,4 +220,3 @@ string printdiagnostics(FlowField& u, const DNS& dns, Real t, const TimeStep& dt sout << " CFL == " << cfl << endl; return sout.str(); } - From 5f09cca86732a9b7b00aa20937eaa4175401c9bf Mon Sep 17 00:00:00 2001 From: Florian Reetz Date: Tue, 26 May 2020 10:42:06 +0200 Subject: [PATCH 10/10] Added the benchmark example program --- CMakeLists.txt | 1 + modules/ilc/examples/CMakeLists.txt | 6 + modules/ilc/examples/benchmark_ilc.cpp | 166 +++++++++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 modules/ilc/examples/CMakeLists.txt create mode 100644 modules/ilc/examples/benchmark_ilc.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e9d5d052..592db256 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -197,6 +197,7 @@ if (WITH_ILC) endif() add_subdirectory(modules/ilc) add_subdirectory(modules/ilc/programs) + add_subdirectory(modules/ilc/examples) endif () # Check if we want to build the python wrapper and have boost-python diff --git a/modules/ilc/examples/CMakeLists.txt b/modules/ilc/examples/CMakeLists.txt new file mode 100644 index 00000000..6a5aedb9 --- /dev/null +++ b/modules/ilc/examples/CMakeLists.txt @@ -0,0 +1,6 @@ +set(ilc_EXAMPLES benchmark_ilc) + +foreach (program ${ilc_EXAMPLES}) + install_channelflow_application(${program} examples) + target_link_libraries(${program}_app PUBLIC ilc) +endforeach (program) diff --git a/modules/ilc/examples/benchmark_ilc.cpp b/modules/ilc/examples/benchmark_ilc.cpp new file mode 100644 index 00000000..a156458e --- /dev/null +++ b/modules/ilc/examples/benchmark_ilc.cpp @@ -0,0 +1,166 @@ +/** + * The classic benchmark program, like for pure shear flow + * + * This file is a part of channelflow version 2.0, https://channelflow.ch . + * License is GNU GPL version 2 or later: ./LICENSE + * + * Original author: Florian Reetz + */ + +#include +#include "channelflow/flowfield.h" +#include "modules/ilc/ilc.h" + +#ifdef HAVE_MPI +#include +#endif +#include "channelflow/cfmpi.h" + +using namespace std; +using namespace chflow; + +int main(int argc, char* argv[]) { + cfMPI_Init(&argc, &argv); + { + int taskid = 0; +#ifdef HAVE_MPI + MPI_Comm_rank(MPI_COMM_WORLD, &taskid); +#endif + if (taskid == 0) + cout << "Starting channelflow benchmark" << endl; + + string purpose( + "This program loads the field uinit from the harddisk and integrates\nit for 10 time units. The resulting " + "field is compared to the field\nufinal. Wall-clock time elapsed for each timeunit as well as an\naverage " + "are calculated."); + ArgList args(argc, argv, purpose); + int nproc0 = args.getint("-np0", "-nproc0", 0, "number of processes for transpose/parallel ffts"); + int nproc1 = args.getint("-np1", "-nproc1", 0, "number of processes for slice fft"); + bool fftwmeasure = args.getflag("-fftwmeasure", "--fftwmeasure", "use fftw_measure instead of fftw_patient"); + bool fftwwisdom = args.getflag("-fftwwisdom", "--fftwwisdom", "try loading fftw wisdom"); + bool saveresult = args.getflag("-s", "--saveresult", "save resulting field to uresult.nc"); + string dir = args.getstr("-d", "--directory", ".", "directory where fields are found and stored"); + args.check(); + + if (taskid == 0) + cout << "Creating CfMPI object..." << flush; + CfMPI* cfmpi = &CfMPI::getInstance(nproc0, nproc1); + if (taskid == 0) + cout << "done" << endl; + + if (taskid == 0) + cout << "Loading FlowFields..." << endl; + FlowField u(dir + "/uinit", cfmpi); + FlowField temp(dir + "/tinit", cfmpi); + if (taskid == 0) + cout << "done" << endl; + + if (u.taskid() == 0) + cout << "Calculating l2norm..." << flush; + Real ul2n = L2Norm(u); + Real tl2n = L2Norm(temp); + if (taskid == 0) + cout << "done" << endl; + + if (u.taskid() == 0) { + cout << "================================================================\n"; + cout << purpose << endl << endl; + cout << "Distribution of processes is " << u.nproc0() << "x" << u.nproc1() << endl; + } + if (fftwwisdom && u.taskid() == 0) { + cout << "Loading fftw wisdom" << endl; + fftw_loadwisdom(); + } + if (u.taskid() == 0) + cout << "Optimizing FFTW..." << flush; + { + FlowField utmp(u); + if (fftwmeasure) + utmp.optimizeFFTW(FFTW_MEASURE); + else + utmp.optimizeFFTW(FFTW_PATIENT); + fftw_savewisdom(); + } + if (u.taskid() == 0) + cout << "done" << endl; + ul2n = L2Norm(u); + tl2n = L2Norm(temp); + if (u.taskid() == 0) + cout << " L2Norm(u) == " << ul2n << endl; + if (u.taskid() == 0) + cout << " L2Norm(T) == " << tl2n << endl; + + // Define integration parameters + const int n = 40; // take n steps between printouts + const Real dt = 1.0 / n; // integration timestep + + // Define DNS parameters + ILCFlags flags; + + // Run at default flags. If you change them, recreate the test files. + flags.dt = dt; + flags.verbosity = Silent; + + if (u.taskid() == 0) + cout << "Building FlowField q..." << flush; + vector fields = { + u, temp, FlowField(u.Nx(), u.Ny(), u.Nz(), 1, u.Lx(), u.Lz(), u.a(), u.b(), u.cfmpi(), Spectral, Spectral)}; + if (u.taskid() == 0) + cout << "done" << endl; + if (u.taskid() == 0) + cout << "Building dns..." << flush; + ILC ilc(fields, flags); + if (u.taskid() == 0) + cout << "done" << endl; + + Real avtime = 0; + int i = 0; + Real T = 10; + for (Real t = 0; t <= T; t += 1) { + timeval start, end; + gettimeofday(&start, 0); + Real cfl = ilc.CFL(fields[0]); + if (fields[0].taskid() == 0) + cout << " t == " << t << endl; + if (fields[0].taskid() == 0) + cout << " CFL == " << cfl << endl; + Real ul2n = L2Norm(fields[0]); + Real tl2n = L2Norm(fields[1]); + if (fields[0].taskid() == 0) + cout << " L2Norm(u) == " << ul2n << endl; + if (fields[0].taskid() == 0) + cout << " L2Norm(T) == " << tl2n << endl; + + // Take n steps of length dt + ilc.advance(fields, n); + + gettimeofday(&end, 0); + Real sec = (Real)(end.tv_sec - start.tv_sec); + Real ms = (((Real)end.tv_usec) - ((Real)start.tv_usec)); + Real timeused = sec + ms / 1000000.; + if (fields[0].taskid() == 0) + cout << "duration for this timeunit: " << timeused << endl; + if (t != 0) { + avtime += timeused; + i++; + } + if (fields[0].taskid() == 0) + cout << endl; + } + + if (fields[0].taskid() == 0) { + cout << "Average time/timeunit: " << avtime / i << "s" << endl; + ofstream fout("benchmark_results", ios::app); + fout << "np0 x np1 == " << cfmpi->nproc0() << " x " << cfmpi->nproc1() << endl; + fout << "fftw_flag == " << (fftwmeasure ? "fftw_measure" : "fftw_patient") << endl; + fout << "Average time/timeunit: " << avtime / i << "s" << endl << endl; + fout.close(); + } + // fftw_mpi_gather_wisdom(MPI_COMM_WORLD); + fftw_savewisdom(); + if (saveresult) + fields[0].save(dir + "/uresult.nc"); + } + cfMPI_Finalize(); + return 0; +}