OPAL (Object Oriented Parallel Accelerator Library)  2.2.0
OPAL
MultiBunchHandler.cpp
Go to the documentation of this file.
1 #include "MultiBunchHandler.h"
2 
4 
5 //FIXME Remove headers and dynamic_cast in
6 #include "Algorithms/PartBunch.h"
7 #ifdef ENABLE_AMR
9 #endif
10 
11 extern Inform *gmsg;
12 
14  const int& numBunch,
15  const double& eta,
16  const double& para,
17  const std::string& mode,
18  const std::string& binning)
19  : onebunch_m(OpalData::getInstance()->getInputBasename() + "-onebunch.h5")
20  , numBunch_m(numBunch)
21  , eta_m(eta)
22  , coeffDBunches_m(para)
23  , radiusLastTurn_m(0.0)
24  , radiusThisTurn_m(0.0)
25  , bunchCount_m(1)
26 {
27  PAssert_GT(numBunch, 1);
28 
29  binfo_m.reserve(numBunch);
30  for (int i = 0; i < beam->getNumBunch(); ++i) {
31  binfo_m.push_back(beaminfo_t());
32  }
33 
34  this->setBinning(binning);
35 
36  // mode of generating new bunches:
37  // "FORCE" means generating one bunch after each revolution, until get "TURNS" bunches.
38  // "AUTO" means only when the distance between two neighbor bunches is below the limitation,
39  // then starts to generate new bunches after each revolution,until get "TURNS" bunches;
40  // otherwise, run single bunch track
41 
42  *gmsg << "***---------------------------- MULTI-BUNCHES MULTI-ENERGY-BINS MODE "
43  << "----------------------------*** " << endl;
44 
45  // only for regular run of multi bunches, instantiate the PartBins class
46  // note that for restart run of multi bunches, PartBins class is instantiated in function
47  // Distribution::doRestartOpalCycl()
48  if (!OpalData::getInstance()->inRestartRun()) {
49 
50  // already exist bins number initially
51  const int BinCount = 1;
52  //allowed maximal bin
53  const int MaxBinNum = 1000;
54 
55  // initialize particles number for each bin (both existed and not yet emmitted)
56  size_t partInBin[MaxBinNum] = {0};
57  partInBin[0] = beam->getTotalNum();
58 
59  beam->setPBins(new PartBinsCyc(MaxBinNum, BinCount, partInBin));
60  // the allowed maximal bin number is set to 100
61  //beam->setPBins(new PartBins(100));
62 
63  this->setMode(mode);
64 
65  } else {
66  if(beam->pbin_m->getLastemittedBin() < 2) {
67  *gmsg << "In this restart job, the multi-bunches mode is forcely set to AUTO mode." << endl;
69  } else {
70  *gmsg << "In this restart job, the multi-bunches mode is forcely set to FORCE mode." << endl
71  << "If the existing bunch number is less than the specified number of TURN, "
72  << "readin the phase space of STEP#0 from h5 file consecutively" << endl;
74  }
75  }
76 }
77 
78 
80 {
81  static IpplTimings::TimerRef saveBunchTimer = IpplTimings::getTimer("Save Bunch H5");
82  IpplTimings::startTimer(saveBunchTimer);
83  *gmsg << endl;
84  *gmsg << "* Store beam to H5 file for multibunch simulation ... ";
85 
86  Ppos_t coord, momentum;
87  ParticleAttrib<double> mass, charge;
89 
90  std::size_t localNum = beam->getLocalNum();
91 
92  coord.create(localNum);
93  coord = beam->R;
94 
95  momentum.create(localNum);
96  momentum = beam->P;
97 
98  mass.create(localNum);
99  mass = beam->M;
100 
101  charge.create(localNum);
102  charge = beam->Q;
103 
104  ptype.create(localNum);
105  ptype = beam->PType;
106 
107  std::map<std::string, double> additionalAttributes = {
108  std::make_pair("REFPR", 0.0),
109  std::make_pair("REFPT", 0.0),
110  std::make_pair("REFPZ", 0.0),
111  std::make_pair("REFR", 0.0),
112  std::make_pair("REFTHETA", 0.0),
113  std::make_pair("REFZ", 0.0),
114  std::make_pair("AZIMUTH", 0.0),
115  std::make_pair("ELEVATION", 0.0),
116  std::make_pair("B-ref_x", 0.0),
117  std::make_pair("B-ref_z", 0.0),
118  std::make_pair("B-ref_y", 0.0),
119  std::make_pair("E-ref_x", 0.0),
120  std::make_pair("E-ref_z", 0.0),
121  std::make_pair("E-ref_y", 0.0),
122  std::make_pair("B-head_x", 0.0),
123  std::make_pair("B-head_z", 0.0),
124  std::make_pair("B-head_y", 0.0),
125  std::make_pair("E-head_x", 0.0),
126  std::make_pair("E-head_z", 0.0),
127  std::make_pair("E-head_y", 0.0),
128  std::make_pair("B-tail_x", 0.0),
129  std::make_pair("B-tail_z", 0.0),
130  std::make_pair("B-tail_y", 0.0),
131  std::make_pair("E-tail_x", 0.0),
132  std::make_pair("E-tail_z", 0.0),
133  std::make_pair("E-tail_y", 0.0)
134  };
135 
136  H5PartWrapperForPC h5wrapper(onebunch_m, H5_O_WRONLY);
137  h5wrapper.writeHeader();
138  h5wrapper.writeStep(beam, additionalAttributes);
139  h5wrapper.close();
140 
141  *gmsg << "Done." << endl;
142  IpplTimings::stopTimer(saveBunchTimer);
143 }
144 
145 
147  const PartData& ref)
148 {
149  static IpplTimings::TimerRef readBunchTimer = IpplTimings::getTimer("Read Bunch H5");
150  IpplTimings::startTimer(readBunchTimer);
151  *gmsg << endl;
152  *gmsg << "* Read beam from H5 file for multibunch simulation ... ";
153 
154  std::size_t localNum = beam->getLocalNum();
155 
156  /*
157  * 2nd argument: 0 --> step zero is fine since the file has only this step
158  * 3rd argument: "" --> onebunch_m is used
159  * 4th argument: H5_O_RDONLY does not work with this constructor
160  */
161  H5PartWrapperForPC h5wrapper(onebunch_m, 0, "", H5_O_WRONLY);
162 
163  size_t numParticles = h5wrapper.getNumParticles();
164 
165  const int bunchNum = bunchCount_m - 1;
166 
167  beam->setTotalNumPerBunch(numParticles, bunchNum);
168 
169  if ( numParticles == 0 ) {
170  throw OpalException("MultiBunchHandler::readBunch()",
171  "No particles in file " + onebunch_m + ".");
172  }
173 
174  size_t numParticlesPerNode = numParticles / Ippl::getNodes();
175 
176  size_t firstParticle = numParticlesPerNode * Ippl::myNode();
177  size_t lastParticle = firstParticle + numParticlesPerNode - 1;
178  if (Ippl::myNode() == Ippl::getNodes() - 1)
179  lastParticle = numParticles - 1;
180 
181  PAssert_LT(firstParticle, lastParticle +1);
182 
183  numParticles = lastParticle - firstParticle + 1;
184 
185  beam->setLocalNumPerBunch(numParticles, bunchNum);
186 
187  //FIXME
188  std::unique_ptr<PartBunchBase<double, 3> > tmpBunch = nullptr;
189 #ifdef ENABLE_AMR
190  AmrPartBunch* amrbunch_p = dynamic_cast<AmrPartBunch*>(beam);
191  if ( amrbunch_p != nullptr ) {
192  tmpBunch.reset(new AmrPartBunch(&ref,
193  amrbunch_p->getAmrParticleBase()));
194  } else
195 #endif
196  tmpBunch.reset(new PartBunch(&ref));
197 
198  tmpBunch->create(numParticles);
199 
200  h5wrapper.readStep(tmpBunch.get(), firstParticle, lastParticle);
201  h5wrapper.close();
202 
203  beam->create(numParticles);
204 
205  for(size_t ii = 0; ii < numParticles; ++ ii, ++ localNum) {
206  beam->R[localNum] = tmpBunch->R[ii];
207  beam->P[localNum] = tmpBunch->P[ii];
208  beam->M[localNum] = tmpBunch->M[ii];
209  beam->Q[localNum] = tmpBunch->Q[ii];
210  beam->PType[localNum] = ParticleType::REGULAR;
211  beam->Bin[localNum] = bunchNum;
212  beam->bunchNum[localNum] = bunchNum;
213  }
214 
215  beam->boundp();
216 
217  binfo_m.push_back(beaminfo_t(injection_m));
218 
219  *gmsg << "Done." << endl;
220 
221  IpplTimings::stopTimer(readBunchTimer);
222  return true;
223 }
224 
225 
227  const PartData& ref,
228  bool& flagTransition)
229 {
230  short result = 0;
231  if ((bunchCount_m == 1) && (mode_m == MB_MODE::AUTO) && (!flagTransition)) {
232 
233  // we have still a single bunch
234  beam->setTotalNumPerBunch(beam->getTotalNum(), 0);
235  beam->setLocalNumPerBunch(beam->getLocalNum(), 0);
236 
237  // If all of the following conditions are met, this code will be executed
238  // to check the distance between two neighboring bunches:
239  // 1. Only one bunch exists (bunchCount_m == 1)
240  // 2. We are in multi-bunch mode, AUTO sub-mode (mode_m == 2)
241  // 3. It has been a full revolution since the last check (stepsNextCheck)
242 
243  *gmsg << "* MBM: Checking for automatically injecting new bunch ..." << endl;
244 
245  //beam->R *= Vector_t(0.001); // mm --> m
246  beam->calcBeamParameters();
247  //beam->R *= Vector_t(1000.0); // m --> mm
248 
249  Vector_t Rmean = beam->get_centroid(); // m
250 
251  radiusThisTurn_m = std::hypot(Rmean[0],Rmean[1]);
252 
253  Vector_t Rrms = beam->get_rrms(); // m
254 
255  double XYrms = std::hypot(Rrms[0], Rrms[1]);
256 
257  // If the distance between two neighboring bunches is less than 5 times of its 2D rms size
258  // start multi-bunch simulation, fill current phase space to initialR and initialP arrays
260  // since next turn, start multi-bunches
261  saveBunch(beam);
262  flagTransition = true;
263  }
264 
265  *gmsg << "* MBM: RLastTurn = " << radiusLastTurn_m << " [m]" << endl;
266  *gmsg << "* MBM: RThisTurn = " << radiusThisTurn_m << " [m]" << endl;
267  *gmsg << "* MBM: XYrms = " << XYrms << " [m]" << endl;
268 
270  result = 1;
271  }
272 
273  else if (bunchCount_m < numBunch_m) {
274  // Matthias: SteptoLastInj was used in MtsTracker, removed by DW in GenericTracker
275 
276  // If all of the following conditions are met, this code will be executed
277  // to read new bunch from hdf5 format file:
278  // 1. We are in multi-bunch mode (numBunch_m > 1)
279  // 2. It has been a full revolution since the last check
280  // 3. Number of existing bunches is less than the desired number of bunches
281  // 4. FORCE mode, or AUTO mode with flagTransition = true
282  // Note: restart from 1 < BunchCount < numBunch_m must be avoided.
283  *gmsg << "* MBM: Injecting a new bunch ..." << endl;
284 
285  bunchCount_m++;
286 
287  beam->setNumBunch(bunchCount_m);
288 
289  // read initial distribution from h5 file
290  switch ( mode_m ) {
291  case MB_MODE::FORCE:
292  case MB_MODE::AUTO:
293  readBunch(beam, ref);
294  updateParticleBins(beam);
296  break;
297  default:
298  throw OpalException("MultiBunchHandler::injectBunch()",
299  "We shouldn't be here in single bunch mode.");
300  }
301 
302  Ippl::Comm->barrier();
303 
304  *gmsg << "* MBM: Bunch " << bunchCount_m
305  << " injected, total particle number = "
306  << beam->getTotalNum() << endl;
307  result = 2;
308  }
309  return result;
310 }
311 
312 
314  if (bunchCount_m < 2)
315  return;
316 
317  static IpplTimings::TimerRef binningTimer = IpplTimings::getTimer("Particle Binning");
318  IpplTimings::startTimer(binningTimer);
319  switch ( binning_m ) {
320  case MB_BINNING::GAMMA:
321  beam->resetPartBinID2(eta_m);
322  break;
323  case MB_BINNING::BUNCH:
324  beam->resetPartBinBunch();
325  break;
326  default:
327  beam->resetPartBinID2(eta_m);
328  }
329  IpplTimings::stopTimer(binningTimer);
330 }
331 
332 
333 void MultiBunchHandler::setMode(const std::string& mbmode) {
334  if ( mbmode.compare("FORCE") == 0 ) {
335  *gmsg << "FORCE mode: The multi bunches will be injected consecutively" << endl
336  << " after each revolution, until get \"TURNS\" bunches." << endl;
338  } else if ( mbmode.compare("AUTO") == 0 ) {
339  *gmsg << "AUTO mode: The multi bunches will be injected only when the" << endl
340  << " distance between two neighboring bunches is below" << endl
341  << " the limitation. The control parameter is set to "
342  << coeffDBunches_m << endl;
344  } else
345  throw OpalException("MultiBunchHandler::setMode()",
346  "MBMODE name \"" + mbmode + "\" unknown.");
347 }
348 
349 
350 void MultiBunchHandler::setBinning(std::string binning) {
351 
352  binning = Util::toUpper(binning);
353 
354  if ( binning.compare("BUNCH") == 0 ) {
355  *gmsg << "Use 'BUNCH' injection for binnning." << endl;
357  } else if ( binning.compare("GAMMA") == 0 ) {
358  *gmsg << "Use 'GAMMA' for binning." << endl;
360  } else {
361  throw OpalException("MultiBunchHandler::setBinning()",
362  "MB_BINNING name \"" + binning + "\" unknown.");
363  }
364 }
365 
366 
367 void MultiBunchHandler::setRadiusTurns(const double& radius) {
368  if ( mode_m != MB_MODE::AUTO )
369  return;
370 
371  radiusLastTurn_m = radius;
373 
374  if (OpalData::getInstance()->inRestartRun()) {
375  *gmsg << "Radial position at restart position = ";
376  } else {
377  *gmsg << "Initial radial position = ";
378  }
379  // New OPAL 2.0: Init in m -DW
380  *gmsg << radiusThisTurn_m << " m" << endl;
381 }
382 
383 
385  short bunchNr)
386 {
387  if ( !OpalData::getInstance()->isInOPALCyclMode() ) {
388  return false;
389  }
390 
391  const unsigned long localNum = beam->getLocalNum();
392 
393  long int bunchTotalNum = 0;
394  unsigned long bunchLocalNum = 0;
395 
396  /* container:
397  *
398  * ekin, <x>, <y>, <z>, <p_x>, <p_y>, <p_z>,
399  * <x^2>, <y^2>, <z^2>, <p_x^2>, <p_y^2>, <p_z^2>,
400  * <xp_x>, <y_py>, <zp_z>,
401  * <x^3>, <y^3>, <z^3>, <x^4>, <y^4>, <z^4>
402  */
403  const unsigned int dim = PartBunchBase<double, 3>::Dimension;
404  std::vector<double> local(7 * dim + 1);
405 
406  beaminfo_t& binfo = getBunchInfo(bunchNr);
407 
408  for(unsigned long k = 0; k < localNum; ++k) {
409  if ( beam->bunchNum[k] != bunchNr ) { //|| ID[k] == 0 ) {
410  continue;
411  }
412 
413  ++bunchTotalNum;
414  ++bunchLocalNum;
415 
416  // ekin
417  local[0] += std::sqrt(dot(beam->P[k], beam->P[k]) + 1.0);
418 
419  for (unsigned int i = 0; i < dim; ++i) {
420 
421  double r = beam->R[k](i);
422  double p = beam->P[k](i);
423 
424  // <x>, <y>, <z>
425  local[i + 1] += r;
426 
427  // <p_x>, <p_y, <p_z>
428  local[i + dim + 1] += p;
429 
430  // <x^2>, <y^2>, <z^2>
431  double r2 = r * r;
432  local[i + 2 * dim + 1] += r2;
433 
434  // <p_x^2>, <p_y^2>, <p_z^2>
435  local[i + 3 * dim + 1] += p * p;
436 
437  // <xp_x>, <y_py>, <zp_z>
438  local[i + 4 * dim + 1] += r * p;
439 
440  // <x^3>, <y^3>, <z^3>
441  local[i + 5 * dim + 1] += r2 * r;
442 
443  // <x^4>, <y^4>, <z^4>
444  local[i + 6 * dim + 1] += r2 * r2;
445  }
446  }
447 
448  // inefficient
449  allreduce(bunchTotalNum, 1, std::plus<long int>());
450 
451  // here we also update the number of particles of *this* bunch
452  if (bunchNr >= (short)beam->getNumBunch())
453  throw OpalException("MultiBunchHandler::calcBunchBeamParameters()",
454  "Bunch number " + std::to_string(bunchNr) +
455  " exceeds bunch index " + std::to_string(beam->getNumBunch() - 1));
456 
457  beam->setTotalNumPerBunch(bunchTotalNum, bunchNr);
458  beam->setLocalNumPerBunch(bunchLocalNum, bunchNr);
459 
460  if ( bunchTotalNum == 0 )
461  return false;
462 
463  // ekin
464  const double m0 = beam->getM() * 1.0e-6;
465  local[0] -= bunchLocalNum;
466  local[0] *= m0;
467 
468  allreduce(local.data(), local.size(), std::plus<double>());
469 
470  double invN = 1.0 / double(bunchTotalNum);
471  binfo.ekin = local[0] * invN;
472 
473  binfo.time = beam->getT() * 1e9; // ns
474  binfo.nParticles = bunchTotalNum;
475 
476  for (unsigned int i = 0; i < dim; ++i) {
477 
478  double w = local[i + 1] * invN;
479  double pw = local[i + dim + 1] * invN;
480  double w2 = local[i + 2 * dim + 1] * invN;
481  double pw2 = local[i + 3 * dim + 1] * invN;
482  double wpw = local[i + 4 * dim + 1] * invN;
483  double w3 = local[i + 5 * dim + 1] * invN;
484  double w4 = local[i + 6 * dim + 1] * invN;
485 
486  // <x>, <y>, <z>
487  binfo.mean[i] = w;
488 
489  // central: <p_w^2> - <p_w>^2 (w = x, y, z)
490  binfo.prms[i] = pw2 - pw * pw;
491  if ( binfo.prms[i] < 0 ) {
492  binfo.prms[i] = 0.0;
493  }
494 
495  // central: <wp_w> - <w><p_w>
496  wpw = wpw - w * pw;
497 
498  // central: <w^2> - <w>^2 (w = x, y, z)
499  binfo.rrms[i] = w2 - w * w;
500 
501  // central: normalized emittance
502  binfo.emit[i] = (binfo.rrms[i] * binfo.prms[i] - wpw * wpw);
503  binfo.emit[i] = std::sqrt(std::max(binfo.emit[i], 0.0));
504 
505  // central: <w^4> - 4 * <w> * <w^3> + 6 * <w>^2 * <w^2> - 3 * <w>^4
506  double tmp = w4
507  - 4.0 * w * w3
508  + 6.0 * w * w * w2
509  - 3.0 * w * w * w * w;
510  binfo.halo[i] = tmp / ( binfo.rrms[i] * binfo.rrms[i] );
511 
512  // central: sqrt(<w^2> - <w>^2) (w = x, y, z)
513  binfo.rrms[i] = std::sqrt(binfo.rrms[i]);
514 
515  // central: sqrt(<p_w^2> - <p_w>^2)
516  binfo.prms[i] = std::sqrt(binfo.prms[i]);
517 
518  // central: rms correlation --> (<wp_w> - <w><p_w>) / sqrt(<w^2> * <p_w^2>)
519  double denom = 1.0 / (binfo.rrms[i] * binfo.prms[i]);
520  binfo.correlation[i] = (std::isfinite(denom)) ? wpw * denom : 0.0;
521  }
522 
523  double tmp = 1.0 / std::pow(binfo.ekin / m0 + 1.0, 2.0);
524  binfo.dEkin = binfo.prms[1] * m0 * std::sqrt(1.0 - tmp);
525 
526  return true;
527 }
528 
529 
530 void MultiBunchHandler::updateTime(const double& dt) {
531  for (short b = 0; b < bunchCount_m; ++b) {
532  binfo_m[b].time += dt;
533  }
534 }
535 
536 
537 void MultiBunchHandler::updatePathLength(const std::vector<double>& lpaths) {
538  PAssert_EQ(bunchCount_m, (short)lpaths.size() - 1);
539  for (short b = 0; b < bunchCount_m; ++b) {
540  binfo_m[b].pathlength += lpaths[b];
541  }
542 }
void setBinning(std::string binning)
static int getNodes()
Definition: IpplInfo.cpp:773
ParticleAttrib< Vector_t > P
The global OPAL structure.
Definition: OpalData.h:54
double getT() const
virtual void create(size_t)
void create(size_t M)
bool resetPartBinBunch()
void updateTime(const double &dt)
void setLocalNumPerBunch(size_t numpart, short n)
T::PETE_Expr_t::PETE_Return_t max(const PETE_Expr< T > &expr, NDIndex< D > &loc)
Definition: ReductionLoc.h:123
short injectBunch(PartBunchBase< double, 3 > *beam, const PartData &ref, bool &flagTransition)
short numBunch_m
The number of bunches specified in TURNS of RUN commond.
The base class for all OPAL exceptions.
Definition: OpalException.h:28
ParticleAttrib< double > Q
Vector_t get_rrms() const
Particle reference data.
Definition: PartData.h:38
Inform * gmsg
Definition: Main.cpp:21
std::string toUpper(const std::string &str)
Definition: Util.cpp:130
double getM() const
MultiBunchHandler(PartBunchBase< double, 3 > *beam, const int &numBunch, const double &eta, const double &para, const std::string &mode, const std::string &binning)
void barrier(void)
static int myNode()
Definition: IpplInfo.cpp:794
ParticleAttrib< short > bunchNum
bool calcBunchBeamParameters(PartBunchBase< double, 3 > *beam, short bunchNr)
bool readBunch(PartBunchBase< double, 3 > *beam, const PartData &ref)
void saveBunch(PartBunchBase< double, 3 > *beam)
void setTotalNumPerBunch(size_t numpart, short n)
size_t getTotalNum() const
double dot(const Vector3D &lhs, const Vector3D &rhs)
Vector dot product.
Definition: Vector3D.cpp:118
virtual void writeHeader()
pbase_t * getAmrParticleBase()
#define PAssert_LT(a, b)
Definition: PAssert.h:121
void updatePathLength(const std::vector< double > &lpaths)
static OpalData * getInstance()
Definition: OpalData.cpp:209
virtual void writeStep(PartBunchBase< double, 3 > *, const std::map< std::string, double > &additionalStepAttributes)
void calcBeamParameters()
#define PAssert_EQ(a, b)
Definition: PAssert.h:119
ParticleAttrib< short > PType
virtual void readStep(PartBunchBase< double, 3 > *, h5_ssize_t firstParticle, h5_ssize_t lastParticle)
PartBins * pbin_m
void allreduce(const T *input, T *output, int count, Op op)
Definition: GlobalComm.hpp:510
static void startTimer(TimerRef t)
Definition: IpplTimings.h:187
void setRadiusTurns(const double &radius)
Tps< T > pow(const Tps< T > &x, int y)
Integer power.
Definition: TpsMath.h:76
size_t getLocalNum() const
Vector_t get_centroid() const
Tps< T > sqrt(const Tps< T > &x)
Square root.
Definition: TpsMath.h:91
std::vector< beaminfo_t > binfo_m
#define PAssert_GT(a, b)
Definition: PAssert.h:123
short getNumBunch() const
int getLastemittedBin()
Definition: PartBins.h:202
bool resetPartBinID2(const double eta)
reset Bin[] for each particle according to the method given in paper PAST-AB(064402) by G...
beaminfo_t & getBunchInfo(short bunchNr)
void setNumBunch(short n)
Particle Bunch.
Definition: PartBunch.h:30
ParticleAttrib< int > Bin
virtual void boundp()
ParticleAttrib< double > M
ParticlePos_t & R
Timing::TimerRef TimerRef
Definition: IpplTimings.h:176
static TimerRef getTimer(const char *nm)
Definition: IpplTimings.h:182
static void stopTimer(TimerRef t)
Definition: IpplTimings.h:192
Definition: Inform.h:41
size_t getNumParticles() const
static Communicate * Comm
Definition: IpplInfo.h:93
void setMode(const std::string &mbmode)
set the working sub-mode for multi-bunch mode: &quot;FORCE&quot; or &quot;AUTO&quot;
void setPBins(PartBins *pbin)
Inform & endl(Inform &inf)
Definition: Inform.cpp:42
void updateParticleBins(PartBunchBase< double, 3 > *beam)