//
// Class GreenWakeFunction
//
// Copyright (c) 2008 - 2020, Paul Scherrer Institut, Villigen PSI, Switzerland
// All rights reserved
//
// This file is part of OPAL.
//
// OPAL is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// You should have received a copy of the GNU General Public License
// along with OPAL. If not, see .
//
#include "Solvers/GreenWakeFunction.hh"
#include "Algorithms/PartBunchBase.h"
#include "Utilities/GeneralClassicException.h"
#include
#include
#include
#include
#include // Needed for stream I/O
#include // Needed for I/O manipulators
#include "gsl/gsl_fft_real.h"
#include "gsl/gsl_fft_halfcomplex.h"
//IFF: TEST
//#define ENABLE_WAKE_DEBUG
//#define ENABLE_WAKE_DUMP
//#define ENABLE_WAKE_TESTS_FFT_OUT
/**
*
* @todo In this code one can only apply either the longitudinal wakefield or the transversal wakefield. One should implement that both wakefields can be applied to the particle beam
* @todo NBins must be set equal to MT of the fieldsolver. This should be changed. (the length of lineDensity_m must be NBins and not to MT)
*
* @param[in] NBIN number of Bins
* @param[in] Z0 impedance of the tube
* @param[in] radius radius of the tube
* @param[in] sigma material constant
* @param[in] acMode 1 for AC and 2 for DC
* @param[in] tau material constant
* @param[in] direction 0 for transversal and 1 for Longitudinal
* @param[in] constLength true if the length of the particle bunch is considered as constant
* @param[in] fname read wake from file
*/
GreenWakeFunction::GreenWakeFunction(const std::string &name,
std::vector filters,
int NBIN,
double Z0,
double radius,
double sigma,
int acMode,
double tau,
int direction,
bool constLength,
std::string fname):
WakeFunction(name, NBIN),
lineDensity_m(),
//~ FftWField_m(0),
NBin_m(NBIN),
Z0_m(Z0),
radius_m(radius),
sigma_m(sigma),
acMode_m(acMode),
tau_m(tau),
direction_m(direction),
constLength_m(constLength),
filename_m(fname),
filters_m(filters.begin(), filters.end()) {
#ifdef ENABLE_WAKE_DEBUG
*gmsg << "* ************* W A K E ************************************************************ " << endl;
*gmsg << "* Entered GreenWakeFunction::GreenWakeFunction " << '\n';
*gmsg << "* ********************************************************************************** " << endl;
#endif
}
GreenWakeFunction::~GreenWakeFunction() {
//~ if(FftWField_m != 0) {
//~ delete[] FftWField_m;
//~ }
}
/**
* @brief given a vector of length N, distribute the indexes among the available processors
*
*
* @todo make this function general available
*
*
* @param[in] length of vector
* @param[out] first: lowIndex, second: hiIndex
*/
std::pair GreenWakeFunction::distrIndices(int vectLen) {
std::pair dist;
//IFF: properly distribute remainder
int rem = vectLen - (vectLen / Ippl::getNodes()) * Ippl::getNodes();
int tmp = (rem > Ippl::myNode()) ? 1 : 0;
int locBunchRange = vectLen / Ippl::getNodes() + tmp;
dist.first = locBunchRange * Ippl::myNode() + (1 - tmp) * rem;
dist.second = dist.first + locBunchRange - 1;
return dist;
}
void GreenWakeFunction::apply(PartBunchBase *bunch) {
Vector_t rmin, rmax;
double charge = bunch->getChargePerParticle();
// CKR: was bunch->Q[1] changed it;
//FIXME: why 1? bunch,getTotalCharge()
// or bunch->getChargePerParticle()?
double K = 0; // constant to normalize the lineDensity_m to 1
double spacing, mindist;
std::vector OutEnergy(NBin_m);
bunch->calcBeamParameters();
bunch->get_bounds(rmin, rmax);
//FIXME IFF: do we have unitless r's here? is that what we want?
mindist = rmin(2);
switch(direction_m) {
case LONGITUDINAL:
spacing = std::abs(rmax(2) - rmin(2));
break; //FIXME: Kann mann das Spacing immer ändern?
case TRANSVERSAL:
spacing = rmax(0) * rmax(0) + rmax(1) * rmax(1);
break;
default:
throw GeneralClassicException("GreenWakeFunction", "invalid direction specified");
}
assert(NBin_m > 0);
spacing /= (NBin_m - 1); //FIXME: why -1? CKR: because grid spacings = grid points - 1
// Calculate the Wakefield if needed
if(FftWField_m.empty()) {
FftWField_m.resize(2*NBin_m-1);
if(filename_m != "") {
setWakeFromFile(NBin_m, spacing);
} else {
CalcWakeFFT(spacing);
}
} else if(!constLength_m) {
CalcWakeFFT(spacing);
}
// Calculate the line density of the particle bunch
std::pair meshInfo;
bunch->calcLineDensity(nBins_m, lineDensity_m, meshInfo);
#ifdef ENABLE_WAKE_DEBUG
*gmsg << "* ************* W A K E ************************************************************ " << endl;
*gmsg << "* GreenWakeFunction::apply lineDensity_m.size() = " << lineDensity_m.size() << endl;
*gmsg << "* ********************************************************************************** " << endl;
#endif
// smooth the line density of the particle bunch
for(std::vector::const_iterator fit = filters_m.begin(); fit != filters_m.end(); ++fit) {
(*fit)->apply(lineDensity_m);
}
for(unsigned int i = 0; i < lineDensity_m.size(); i++) {
K += lineDensity_m[i];
}
K = 1 / K;
// compute the kick due to the wakefield
compEnergy(K, charge, lineDensity_m, OutEnergy.data());
// Add the right OutEnergy[i] to all the particles
//FIXME: can we specify LONG AND TRANS?
switch(direction_m) {
case LONGITUDINAL:
for(unsigned int i = 0; i < bunch->getLocalNum(); i++) {
//FIXME: Stimmt das????????? (von den einheiten)
// calculate bin containing particle
int idx = (int)(floor((bunch->R[i](2) - mindist) / spacing));
//IFF: should be ok
if(idx == NBin_m) idx--;
assert(idx >= 0 && idx < NBin_m);
double dE = OutEnergy[idx];
bunch->Ef[i](2) += dE;
}
break;
case TRANSVERSAL:
for(unsigned int i = 0; i < bunch->getLocalNum(); i++) {
// calculate bin containing particle
int idx = (int)(floor((bunch->R[i](2) - mindist) / spacing));
//IFF: should be ok
if(idx == NBin_m) idx--;
assert(idx >= 0 && idx < NBin_m);
double dE = OutEnergy[idx];
// ACHTUNG spacing auch in transversal richtung
double dist = sqrt(bunch->R[i](0) * bunch->R[i](0) + bunch->R[i](1) * bunch->R[i](1));
assert(dist > 0);
bunch->Ef[i](0) += dE * bunch->R[i](0) / dist;
bunch->Ef[i](1) += dE * bunch->R[i](1) / dist;
}
break;
default:
throw GeneralClassicException("GreenWakeFunction", "invalid direction specified");
}
#ifdef ENABLE_WAKE_DUMP
ofstream f2("OutEnergy.dat");
f2 << "# Energy of the Wake calculated in Opal\n"
<< "# Z0 = " << Z0_m << "\n"
<< "# radius = " << radius << "\n"
<< "# sigma = " << sigma << "\n"
<< "# c = " << c << "\n"
<< "# acMode = " << acMode << "\n"
<< "# tau = " << tau << "\n"
<< "# direction = " << direction << "\n"
<< "# spacing = " << spacing << "\n"
<< "# Lbunch = " << NBin_m << "\n";
for(int i = 0; i < NBin_m; i++) {
f2 << i + 1 << " " << OutEnergy[i] << "\n";
}
f2.flush();
f2.close();
#endif
}
/**
* @brief just a Testfunction! Calculate the energy of the Wakefunction with the lambda
*
*
* @param[in] K a constant
* @param[in] charge a constant
* @param[in] lambda the distribution of the Particles
* @param[out] OutEnergy this is the Output
*/
void GreenWakeFunction::compEnergy(const double K,
const double charge,
const double *lambda,
double *OutEnergy) {
int N = 2 * NBin_m - 1;
// Allocate Space for the zero padded lambda and its Fourier Transformed
std::vector pLambda(N);
gsl_fft_halfcomplex_wavetable *hc;
gsl_fft_real_wavetable *real = gsl_fft_real_wavetable_alloc(N);
gsl_fft_real_workspace *work = gsl_fft_real_workspace_alloc(N);
// fill the arrays with data
for(int i = 0; i < NBin_m; i ++) {
pLambda[N-i-1] = 0.0;
pLambda[i] = lambda[i];
}
//FFT of the lambda
gsl_fft_real_transform(pLambda.data(), 1, N, real, work);
gsl_fft_real_wavetable_free(real);
// convolution -> multiplication in Fourier space
pLambda[0] *= FftWField_m[0];
for(int i = 1; i < N; i += 2) {
double temp = pLambda[i];
pLambda[i] = FftWField_m[i] * pLambda[i] - FftWField_m[i+1] * pLambda[i+1];
pLambda[i+1] = FftWField_m[i] * pLambda[i+1] + FftWField_m[i+1] * temp;
}
// inverse transform to get c, the convolution of a and b;
hc = gsl_fft_halfcomplex_wavetable_alloc(N);
gsl_fft_halfcomplex_inverse(pLambda.data(), 1, N, hc, work);
// Write the result to the output:
for(int i = 0; i < NBin_m; i ++) {
OutEnergy[i] = -charge * K * pLambda[i] / (2.0 * NBin_m) * N; // CKR: I doubt that the multiplication with N is correct,
// put it here to get the same result as with FFTW
// My suspicion: S. Pauli has forgotten that if you
// do an fft followed by an inverse fft you'll get
// N times your original data
}
gsl_fft_halfcomplex_wavetable_free(hc);
gsl_fft_real_workspace_free(work);
}
/**
* @brief Calculate the energy of the Wakefunction with the lambda
*
*
* @param[in] K a constant
* @param[in] charge a constant
* @param[in] lambda the distribution of the Particles
* @param[out] OutEnergy this is the Output
*
*/
void GreenWakeFunction::compEnergy(const double K,
const double charge,
std::vector lambda,
double *OutEnergy) {
int N = 2 * NBin_m - 1;
// Allocate Space for the zero padded lambda and its Fourier Transformed
std::vector pLambda(N);
gsl_fft_halfcomplex_wavetable *hc;
gsl_fft_real_wavetable *real = gsl_fft_real_wavetable_alloc(N);
gsl_fft_real_workspace *work = gsl_fft_real_workspace_alloc(N);
// fill the arrays with data
for(int i = 0; i < NBin_m; i ++) {
pLambda[N-i-1] = 0.0;
pLambda[i] = lambda[i];
}
//FFT of the lambda
gsl_fft_real_transform(pLambda.data(), 1, N, real, work);
gsl_fft_real_wavetable_free(real);
// Convolution -> just a multiplication in Fourier space
pLambda[0] *= FftWField_m[0];
for(int i = 1; i < N; i += 2) {
double temp = pLambda[i];
pLambda[i] = FftWField_m[i] * pLambda[i] - FftWField_m[i+1] * pLambda[i+1];
pLambda[i+1] = FftWField_m[i] * pLambda[i+1] + FftWField_m[i+1] * temp;
}
// IFFT
hc = gsl_fft_halfcomplex_wavetable_alloc(N);
gsl_fft_halfcomplex_inverse(pLambda.data(), 1, N, hc, work);
// Write the result to the output:
for(int i = 0; i < NBin_m; i ++) {
OutEnergy[i] = -charge * K * pLambda[i] / (2.0 * NBin_m) * N; // CKR: I doubt that the multiplication with N is correct,
// put it here to get the same result as with FFTW
// My suspicion: S. Pauli has forgotten that if you
// do an fft followed by an inverse fft you'll get
// N times your original data
}
gsl_fft_halfcomplex_wavetable_free(hc);
gsl_fft_real_workspace_free(work);
}
/**
* @brief Calculate the FFT of the Wakefunction
*
* @param[in] spacing distance between 2 slice in the line distribution
*
*/
void GreenWakeFunction::CalcWakeFFT(double spacing) {
// Set integration properties
double a = 1, b = 1000000;
unsigned int N = 1000000;
int M = 2 * NBin_m - 1;
gsl_fft_real_wavetable *real = gsl_fft_real_wavetable_alloc(M);
gsl_fft_real_workspace *work = gsl_fft_real_workspace_alloc(M);
const std::pair myDist = distrIndices(NBin_m);
const int lowIndex = myDist.first;
const int hiIndex = myDist.second;
for(int i = 0; i < M; i ++) {
FftWField_m[i] = 0.0;
}
/**
Calculate the Wakefield on all processors
*/
// if (Ippl::myNode() != Ippl::getNodes()-1) {
for(int i = lowIndex; i <= hiIndex; i ++) {
Wake w(i * spacing, Z0_m, radius_m, sigma_m, acMode_m, tau_m, direction_m);
FftWField_m[i] = simpson(w, a, b, N);
}
// } else {
// //IFF: changed to <= with new distr
// for (int i = lowIndex; i <= hiIndex; i ++) {
// Wake w(i*spacing, Z0_m, radius_m, sigma_m, acMode_m, tau_m, direction_m);
// FftWField[i] = simpson(w,a,b,N);
// }
// }
/**
Reduce the results
*/
reduce(&(FftWField_m[0]), &(FftWField_m[0]) + NBin_m, &(FftWField_m[0]), OpAddAssign());
#ifdef ENABLE_WAKE_TESTS_FFT_OUT
std::vector wf(2*NBin_m-1);
for(int i = 0; i < 2 * NBin_m - 1; ++ i) {
wf[i] = FftWField_m[i];
}
#endif
// calculate the FFT of the Wakefield
gsl_fft_real_transform(FftWField_m.data(), 1, M, real, work);
#ifdef ENABLE_WAKE_TESTS_FFT_OUT
ofstream f2("FFTwake.dat");
f2 << "# FFT of the Wake calculated in Opal" << "\n"
<< "# Z0 = " << Z0_m << "\n"
<< "# radius = " << radius_m << "\n"
<< "# sigma = " << sigma_m << "\n"
<< "# mode = " << acMode_m << "\n"
<< "# tau = " << tau_m << "\n"
<< "# direction = " << direction_m << "\n"
<< "# spacing = " << spacing << "\n"
<< "# Lbunch = " << NBin_m << "\n";
f2 << "0\t" << FftWField_m[0] << "\t0.0\t" << wf[0] << "\n";
for(int i = 1; i < M; i += 2) {
f2 << (i + 1) / 2 << "\t"
<< FftWField_m[i] << "\t"
<< FftWField_m[i + 1] << "\t"
<< wf[(i+1)/2] << "\n";
}
f2.flush();
f2.close();
#endif
gsl_fft_real_wavetable_free(real);
gsl_fft_real_workspace_free(work);
}
/**
* @brief reads in the wakefield from file
*/
void GreenWakeFunction::setWakeFromFile(int NBin_m, double spacing) {
Inform msg("readSDDS ");
std::string name;
char temp[256];
int Np;
double dummy;
gsl_fft_real_wavetable *real;
gsl_fft_real_workspace *work;
std::ifstream fs;
fs.open(filename_m.c_str());
if(fs.fail()) {
throw GeneralClassicException("GreenWakeFunction::setWakeFromFile",
"Open file operation failed, please check if \""
+ filename_m + "\" really exists.");
}
fs >> name;
msg << " SSDS1 read = " << name << endl;
if(name.compare("SDDS1") != 0) {
throw GeneralClassicException("GreenWakeFunction::setWakeFromFile",
" No SDDS1 File. A SDDS1 file should start with a SDDS1 String. Check file \""
+ filename_m + "\" ");
}
for(int i = 0; i < 6; i++) {
fs.getline(temp, 256);
msg << "line " << i << " : " << temp << endl;
}
fs >> Np;
msg << " header read" << endl;
if(Np <= 0) {
throw GeneralClassicException("GreenWakeFunction::setWakeFromFile",
" The particle number should be bigger than zero! Please check the first line of file \""
+ filename_m + "\".");
}
msg << " Np = " << Np << endl;
std::vector wake(Np);
std::vector dist(Np);
// read the wakefunction
for(int i = 0; i < Np; i ++) {
if(!fs.eof()) {
fs >> dist[i] >> wake[i] >> dummy;
}
if(fs.eof()) {
throw GeneralClassicException("GreenWakeFunction::setWakeFromFile",
" End of file reached before the whole wakefield is imported, please check file \""
+ filename_m + "\".");
}
}
// if needed interpolate the wake in a way that the wake form the file fits to the wake needs in the code (??)
FftWField_m.resize(NBin_m);
for(int i = 0; i < NBin_m; i ++) {
int j = 0;
while(dist[j] < i * spacing) {
j++;
}
-- j; // i * spacing should probably between dist[j] and dist[j+1]
// linear interpolation
FftWField_m[i] = wake[j] + ((wake[j+1] - wake[j]) / (dist[j+1] - dist[j]) * (i * spacing - dist[j]));
}
real = gsl_fft_real_wavetable_alloc(NBin_m);
work = gsl_fft_real_workspace_alloc(NBin_m);
gsl_fft_real_transform(FftWField_m.data(), 1, NBin_m, real, work);
gsl_fft_real_wavetable_free(real);
gsl_fft_real_workspace_free(work);
}
const std::string GreenWakeFunction::getType() const {
return "GreenWakeFunction";
}