OPAL (Object Oriented Parallel Accelerator Library) 2022.1
OPAL
ClosedOrbitFinder.h
Go to the documentation of this file.
1//
2// Class ClosedOrbitFinder
3// This class finds a closed orbit of a cyclotron for a given energy.
4// The algorithm is based on the paper of M. M. Gordon: "Computation of
5// closed orbits and basic focusing properties for sector-focused cyclotrons
6// and the design of 'cyclops'" (1983)
7// As template arguments one chooses the type of the variables and the
8// integrator for the ODEs. The supported steppers can be found on
9// http://www.boost.org/ where it is part of the library Odeint.
10//
11// Copyright (c) 2014, Matthias Frey, ETH Zürich
12// All rights reserved
13//
14// Implemented as part of the Semester thesis by Matthias Frey
15// "Matched Distributions in Cyclotrons"
16//
17// This file is part of OPAL.
18//
19// OPAL is free software: you can redistribute it and/or modify
20// it under the terms of the GNU General Public License as published by
21// the Free Software Foundation, either version 3 of the License, or
22// (at your option) any later version.
23//
24// You should have received a copy of the GNU General Public License
25// along with OPAL. If not, see <https://www.gnu.org/licenses/>.
26//
27#ifndef CLOSEDORBITFINDER_H
28#define CLOSEDORBITFINDER_H
29
30#include <algorithm>
31#include <array>
32#include <cmath>
33#include <functional>
34#include <limits>
35#include <numeric>
36#include <string>
37#include <utility>
38#include <vector>
39
40#include "Utilities/Options.h"
42#include "Physics/Physics.h"
43#include "Physics/Units.h"
44
46
48
49// include headers for integration
50#include <boost/numeric/odeint/integrate/integrate_n_steps.hpp>
51#include <boost/filesystem.hpp>
52
53extern Inform *gmsg;
54
55template<typename Value_type, typename Size_type, class Stepper>
57{
58 public:
60 typedef Value_type value_type;
62 typedef Size_type size_type;
64 typedef std::vector<value_type> container_type;
66 typedef std::vector<value_type> state_type;
67
68 typedef std::function<void(const state_type&, state_type&, const double)> function_t;
69
71
82 Cyclotron* cycl, bool domain = true, int Nsectors = 1);
83
86
88 container_type getPathLength(const value_type& angle = 0);
89
91 container_type getFieldIndex(const value_type& angle = 0);
92
94 std::pair<value_type,value_type> getTunes();
95
100
104
109
114
117
120
123
125
133 bool findOrbit(value_type accuracy, size_type maxit,
134 value_type ekin,
135 value_type dE = 1.0, value_type rguess = -1.0,
136 bool isTuneMode = false);
137
139 void computeOrbitProperties(const value_type& E);
140
141 private:
143
148 value_type computeTune(const std::array<value_type,2>&, value_type, size_type);
149
150 // Compute closed orbit for given energy
152 const value_type&, size_type);
153
155// void computeVerticalOscillations();
156
158 container_type rotate(value_type angle, const container_type& orbitProperty);
159
161 std::array<value_type,2> x_m; // x_m = [x1, x2]
163 std::array<value_type,2> px_m; // px_m = [px1, px2]
165 std::array<value_type,2> z_m; // z_m = [z1, z2]
167 std::array<value_type,2> pz_m; // pz_m = [pz1, pz2]
168
183
187 size_type nzcross_m; //#crossings of zero-line in x- and z-direction
188
191
192 // Is the particle charge [e]
194
196 /* (see paper of Dr. C. Baumgarten: "Transverse-Longitudinal
197 * Coupling by Space Charge in Cyclotrons" (2012), formula (1))
198 */
204
207
210
216 /* value_type lastOrbitVal_m; */
217
223 /* value_type lastMomentumVal_m; */
224
235
238
244 std::function<double(double)> acon_m = [](double wo) { return Physics::c / wo; };
245
247
250 std::function<double(double, double)> bcon_m = [this](double e0, double wo) {
251 return e0 * 1.0e7 / (q_m * Physics::c * Physics::c / wo);
252 };
253
255};
256
257// -----------------------------------------------------------------------------------------------------------------------
258// PUBLIC MEMBER FUNCTIONS
259// -----------------------------------------------------------------------------------------------------------------------
260
261template<typename Value_type, typename Size_type, class Stepper>
262ClosedOrbitFinder<Value_type,
263 Size_type,
265 value_type q,
266 size_type N, Cyclotron* cycl,
267 bool domain, int nSectors)
268 : nxcross_m(0)
269 , nzcross_m(0)
270 , E0_m(E0)
271 , q_m(q)
272 , wo_m(cycl->getRfFrequ()[0]*Units::MHz2Hz/cycl->getCyclHarm()*2.0*Physics::pi)
273 , N_m(N)
274 , dtheta_m(Physics::two_pi/value_type(N))
275 , ravg_m(0)
276 , phase_m(0)
277 /* , lastOrbitVal_m(0.0) */
278 /* , lastMomentumVal_m(0.0) */
279 , domain_m(domain)
280 , nSectors_m(nSectors)
281 , stepper_m()
282 , cycl_m(cycl)
283{
284
285 if ( cycl_m->getFMLowE() > cycl_m->getFMHighE() )
286 throw OpalException("ClosedOrbitFinder::ClosedOrbitFinder()",
287 "Incorrect cyclotron energy (MeV) bounds: Maximum cyclotron energy smaller than minimum cyclotron energy.");
288
289 // if domain_m = true --> integrate over a single sector
290 if (domain_m) {
291 N_m /= cycl_m->getSymmetry();
292 }
293
295
296 // reserve storage for the orbit and momentum (--> size = 0, capacity = N_m+1)
297 /*
298 * we need N+1 storage, since dtheta = 2pi/N (and not 2pi/(N-1)) that's why we need N+1 integration steps
299 * to return to the origin (but the return size is N_m)
300 */
301 r_m.reserve(N_m + 1);
302 pr_m.reserve(N_m + 1);
303 vz_m.reserve(N_m + 1);
304 vpz_m.reserve(N_m + 1);
305
306 // reserve memory of N_m for the properties (--> size = 0, capacity = N_m)
307 h_m.reserve(N_m);
308 ds_m.reserve(N_m);
309 fidx_m.reserve(N_m);
310}
311
312template<typename Value_type, typename Size_type, class Stepper>
315{
316 if (angle != 0.0)
317 return rotate(angle, h_m);
318 else
319 return h_m;
320}
321
322template<typename Value_type, typename Size_type, class Stepper>
325{
326 if (angle != 0.0)
327 return rotate(angle, ds_m);
328 else
329 return ds_m;
330}
331
332template<typename Value_type, typename Size_type, class Stepper>
335{
336 if (angle != 0.0)
337 return rotate(angle, fidx_m);
338 return fidx_m;
339}
340
341template<typename Value_type, typename Size_type, class Stepper>
343 // compute radial tune
344 value_type nur = computeTune(x_m,px_m[1],nxcross_m);
345
346 // compute vertical tune
347 value_type nuz = computeTune(z_m,pz_m[1],nzcross_m);
348
349 return std::make_pair(nur,nuz);
350}
351
352template<typename Value_type, typename Size_type, class Stepper>
355{
356 if (angle != 0.0)
357 return rotate(angle, r_m);
358 else
359 return r_m;
360}
361
362template<typename Value_type, typename Size_type, class Stepper>
365{
366 container_type pr = pr_m;
367
368 if (angle != 0.0)
369 pr = rotate(angle, pr);
370
371 // change units from meters to \beta * \gamma
372 /* in Gordon paper:
373 *
374 * p = \gamma * \beta * a
375 *
376 * where a = c / \omega_{0} with \omega_{0} = 2 * \pi * \nu_{0} = 2 * \pi * \nu_{rf} / h
377 *
378 * c: speed of light
379 * h: harmonic number
380 * v_{rf}: nomial rf frequency
381 *
382 * Units:
383 *
384 * [a] = m --> [p] = m
385 *
386 * The momentum in \beta * \gamma is obtained by dividing by "a"
387 */
388 value_type factor = 1.0 / acon_m(wo_m);
389 std::for_each(pr.begin(), pr.end(), [factor](value_type& p) { p *= factor; });
390
391 return pr;
392}
393
394template<typename Value_type, typename Size_type, class Stepper>
397{
398 return ravg_m;
399}
400
401template<typename Value_type, typename Size_type, class Stepper>
404{
405 return phase_m;
406}
407
408// -----------------------------------------------------------------------------------------------------------------------
409// PRIVATE MEMBER FUNCTIONS
410// -----------------------------------------------------------------------------------------------------------------------
411
412
413
414template<typename Value_type, typename Size_type, class Stepper>
416 size_type maxit,
417 value_type ekin,
418 value_type dE,
419 value_type rguess,
420 bool isTuneMode)
421{
422 /* REMARK TO GORDON
423 * q' = 1/b = 1/bcon
424 * a' = a = acon
425 */
426
427 // resize vectors (--> size = N_m+1, capacity = N_m+1), note: we do N_m+1 integration steps
428 r_m.resize(N_m+1);
429 pr_m.resize(N_m+1);
430 vz_m.resize(N_m+1);
431 vpz_m.resize(N_m+1);
432
433 // store acon locally
434 value_type acon = acon_m(wo_m); // [acon] = m
435 // amplitude of error; Gordon, formula (18) (a = a')
437
438 /*
439 * Christian:
440 * N = 1440 ---> N = 720 ---> dtheta = 2PI/720 --> nsteps = 721
441 *
442 * 0, 2, 4, ... ---> jeden zweiten berechnene: 1, 3, 5, ... interpolieren --> 1440 Werte
443 *
444 * Matthias:
445 * N = 1440 --> dtheta = 2PI/1440 --> nsteps = 1441
446 *
447 * 0, 1, 2, 3, 4, 5, ... --> 1440 Werte
448 *
449 */
450
451 value_type E = cycl_m->getFMLowE(); // starting energy
452 value_type E_fin = ekin; // final energy
453 const value_type eps = dE * 1.0e-1; // articial constant for stopping criteria
454
455 if (isTuneMode) {
456 E_fin = cycl_m->getFMHighE();
457 }
458
459 namespace fs = boost::filesystem;
460 fs::path dir = OpalData::getInstance()->getInputBasename();
461 dir = dir.parent_path() / OpalData::getInstance()->getAuxiliaryOutputDirectory();
462 std::string tunefile = (dir / "tunes.dat").string();
463
464 if ( isTuneMode ) {
465 std::ofstream out(tunefile, std::ios::out);
466
467 out << std::left
468 << std::setw(15) << "# energy[MeV]"
469 << std::setw(15) << "radius_ini[m]"
470 << std::setw(15) << "momentum_ini[Beta Gamma]"
471 << std::setw(15) << "radius_avg[m]"
472 << std::setw(15) << "nu_r"
473 << std::setw(15) << "nu_z"
474 << std::endl;
475 }
476 // initial guess
477 container_type init;
478 enum Guess {NONE, FIRST, SECOND};
479 Guess guess = NONE;
480 value_type rn1 = 0.0, pn1 = 0.0; // normalised r, pr value of previous closed orbit
481 value_type rn2 = 0.0, pn2 = 0.0; // normalised r, pr value of second to previous closed orbit
482
483 // iterate until suggested energy (start with minimum energy)
484 // increase energy by dE
485 *gmsg << level3 << "Start iteration to find closed orbit of energy " << E_fin << " MeV "
486 << "in steps of " << dE << " MeV." << endl;
487
488 for (; E <= E_fin + eps; E += dE) {
489
491
492 // energy dependent values
493 value_type en = E / E0_m; // en = E/E0 = E/(mc^2) (E0 is potential energy)
494 value_type gamma = en + 1.0;
495 value_type gamma2 = gamma * gamma; // = gamma^2
496 value_type beta = std::sqrt(1.0 - 1.0 / gamma2);
497 value_type p = acon_m(wo_m) * std::sqrt(en * (2.0 + en)); // momentum [p] = m; Gordon, formula (3)
498
499 if (guess == NONE) {
500 // set initial values for radius and radial momentum for lowest energy Emin
501 // orbit, [r] = m; Gordon, formula (20)
502 // radial momentum; Gordon, formula (20)
503 // r pr z pz
504 init = {beta * acon, 0.0, 0.0, 1.0};
505 if (rguess >= 0.0) {
506 init[0] = rguess * 0.001;
507 }
508 guess = FIRST;
509 } else if (guess == FIRST) {
510 // new initial values based on previous one, formula (21)
511 init[0] = (beta*acon) * rn1;
512 init[1] = p*pn1;
513 guess = SECOND;
514 } else if (guess == SECOND) {
515 // second extrapolation, formula (21)
516 init[0] = (beta*acon) * (rn1 + (rn1-rn2));
517 init[1] = p*(pn1 + (pn1-pn2));
518 }
519
520 std::fill( r_m.begin(), r_m.end(), 0);
521 std::fill( pr_m.begin(), pr_m.end(), 0);
522 std::fill( vz_m.begin(), vz_m.end(), 0);
523 std::fill(vpz_m.begin(), vpz_m.end(), 0);
524
525 // (re-)set inital values for r and pr
526 r_m[0] = init[0];
527 pr_m[0] = init[1];
528 vz_m[0] = init[2];
529 vpz_m[0] = init[3];
530
531 *gmsg << level3 << " Try to find orbit for " << E << " MeV ... ";
532
533 if ( !this->findOrbitOfEnergy_m(E, init, error, accuracy, maxit) ) {
534 *gmsg << endl << "ClosedOrbitFinder didn't converge for energy " + std::to_string(E) + " MeV." << endl;
535 guess = NONE;
536 continue;
537 }
538
539 *gmsg << level3 << "Successfully found." << endl;
540
541 // store for next initial guess
542 rn2 = rn1;
543 pn2 = pn1;
544 rn1 = r_m[0] / (acon * beta);
545 pn1 = pr_m[0] / p;
546
547 if ( isTuneMode ) {
548
549 this->computeOrbitProperties(E);
550 value_type reo = this->getOrbit( cycl_m->getPHIinit())[0];
551 value_type peo = this->getMomentum(cycl_m->getPHIinit())[0];
552 std::pair<value_type , value_type > tunes = this->getTunes();
553
554 *gmsg << std::left
555 << "* ----------------------------" << endl
556 << "* Closed orbit info (Gordon units):" << endl
557 << "*" << endl
558 << "* kinetic energy: " << std::setw(12) << E << " [MeV]" << endl
559 << "* average radius: " << std::setw(12) << ravg_m << " [m]" << endl
560 << "* initial radius: " << std::setw(12) << reo << " [m]" << endl
561 << "* initial momentum: " << std::setw(12) << peo << " [Beta Gamma]" << endl
562 << "* frequency error: " << phase_m << endl
563 << "* horizontal tune: " << tunes.first << endl
564 << "* vertical tune: " << tunes.second << endl
565 << "* ----------------------------" << endl << endl;
566
567 std::ofstream out(tunefile, std::ios::app);
568 out << std::left
569 << std::setw(15) << E
570 << std::setw(15) << reo
571 << std::setw(15) << peo
572 << std::setw(15) << ravg_m
573 << std::setw(15) << tunes.first
574 << std::setw(15) << tunes.second << std::endl;
575 out.close();
576 }
577 }
578
579 *gmsg << level3 << "Finished closed orbit finder successfully." << endl;
580
581 /* store last entry, since it is needed in computeVerticalOscillations(), because we have to do the same
582 * number of integrations steps there.
583 */
584 /* lastOrbitVal_m = r_m[N_m]; // needed in computeVerticalOscillations() */
585 /* lastMomentumVal_m = pr_m[N_m]; // needed in computeVerticalOscillations() */
586
587 // remove last entry (since we don't have to store [0,2pi], but [0,2pi[) --> size = N_m, capacity = N_m+1
588 r_m.pop_back();
589 pr_m.pop_back();
590
591 /* domain_m = true --> only integrated over a single sector
592 * --> multiply by cycl_m->getSymmetry() to get correct phase_m
593 */
594 if (domain_m)
595 phase_m *= cycl_m->getSymmetry();
596
597 // returns true if converged, otherwise false
598 return error < accuracy;
599}
600
601template<typename Value_type, typename Size_type, class Stepper>
603 const value_type& E,
604 container_type& init,
605 value_type& error,
606 const value_type& accuracy,
607 size_type maxit)
608{
609 /* *gmsg << "rguess : " << init[0] << endl; */
610 /* *gmsg << "prguess: " << init[1] << endl; */
611
612 value_type bint, brint, btint;
613 value_type invbcon = 1.0 / bcon_m(E0_m, wo_m); // [bcon] = MeV*s/(C*m^2) = 10^6 T = 10^7 kG (kilo Gauss)
614
615 value_type xold = 0.0; // for counting nxcross
616 value_type zold = 0.0; // for counting nzcross
617
618 // index for reaching next element of the arrays r and pr (no nicer way found yet)
619 size_type idx = 0;
620 // observer for storing the current value after each ODE step (e.g. Runge-Kutta step) into the containers of r and pr
621 auto store = [&](state_type& y, const value_type /*t*/)
622 {
623 r_m[idx] = y[0];
624 pr_m[idx] = y[1];
625 vz_m[idx] = y[6];
626 vpz_m[idx] = y[7];
627
628 // count number of crossings (excluding starting point --> idx>0)
629 nxcross_m += (idx > 0) * (y[4] * xold < 0);
630 xold = y[4];
631
632 // number of times z2 changes sign
633 nzcross_m += (idx > 0) * (y[10] * zold < 0);
634 zold = y[10];
635
636 ++idx;
637 };
638
639 // define initial state container for integration: y = {r, pr, x1, px1, x2, px2,
640 // z, pz, z1, pz1, z2, pz2,
641 // phase}
642 state_type y(11);
643
644 // difference of last and first value of r (1. element) and pr (2. element)
645 container_type err(2);
646 // correction term for initial values: r = r + dr, pr = pr + dpr; Gordon, formula (17)
647 container_type delta = {0.0, 0.0};
648 // if niterations > maxit --> stop iteration
649 size_type niterations = 0;
650
651 // energy dependent values
652 value_type en = E / E0_m; // en = E/E0 = E/(mc^2) (E0 is potential energy)
653 value_type gamma = en + 1.0;
654 value_type p = acon_m(wo_m) * std::sqrt(en * (2.0 + en)); // momentum [p] = m; Gordon, formula (3)
655 value_type gamma2 = gamma * gamma; // = gamma^2
656 value_type invgamma4 = 1.0 / (gamma2 * gamma2); // = 1/gamma^4
657 value_type p2 = p * p; // p^2 = p*p
658
659 // helper constants
660 value_type pr2; // squared radial momentum (pr^2 = pr*pr)
661 value_type ptheta, invptheta; // azimuthal momentum
662
663 // define the six ODEs (using lambda function)
664 function_t orbit_integration = [&](const state_type &y,
665 state_type &dydt,
666 const double theta)
667 {
668 pr2 = y[1] * y[1];
669 if (p2 < pr2) {
670 //*gmsg << theta << " " << p2 << " " << pr2 << endl;
671 throw OpalException("ClosedOrbitFinder::findOrbitOfEnergy_m()",
672 "p_{r}^2 > p^{2} (defined in Gordon paper) --> Square root of negative number.");
673 }
674 // Gordon, formula (5c)
675 ptheta = std::sqrt(p2 - pr2);
676 invptheta = 1.0 / ptheta;
677 // average field over the number of sectors
678 brint=0.0, btint=0.0, bint=0.0;
679 for (int i = 0; i<nSectors_m; i++) {
680 double angle = theta + i * Physics::two_pi / nSectors_m;
681 double tmpbr, tmpbt, tmpb;
682 // interpolate values of magnetic field
683 cycl_m->apply(y[0], y[6], angle, tmpbr, tmpbt, tmpb);
684 brint += tmpbr;
685 btint += tmpbt;
686 bint += tmpb;
687 }
688 brint /= nSectors_m;
689 btint /= nSectors_m;
690 bint /= nSectors_m;
691 // multiply by 1 / b
692 bint *= invbcon;
693 brint *= invbcon;
694 btint *= invbcon;
695
696 // Gordon, formula (5a)
697 dydt[0] = y[0] * y[1] * invptheta;
698 // Gordon, formula (5b) (typo in paper! second equal sign is a minus)
699 dydt[1] = ptheta - y[0] * bint;
700 // Gordon, formulas (9a) and (9b)
701 for (size_type i = 2; i < 5; i += 2) {
702 dydt[i] = (y[1] * y[i] + y[0] * p2 * y[i+1] * invptheta * invptheta) * invptheta;
703 dydt[i+1] = - y[1] * y[i+1] * invptheta - (bint + y[0] * brint) * y[i];
704 }
705
706 // Gordon, formulas (22a) and (22b)
707 for (size_type i = 6; i < 12; i += 2) {
708 dydt[i] = y[0] * y[i+1] * invptheta;
709 dydt[i+1] = (y[0] * brint - y[1] * invptheta * btint) * y[i];
710 }
711
712 // integrate phase
713 dydt[12] = y[0] * invptheta * gamma - 1;
714
715 };
716
717 // integrate until error smaller than user-defined accuracy
718 do {
719 // (re-)set initial values
720 x_m[0] = 1.0; // x1; Gordon, formula (10)
721 px_m[0] = 0.0; // px1; Gordon, formula (10)
722 x_m[1] = 0.0; // x2; Gordon, formula (10)
723 px_m[1] = 1.0; // px2; Gordon, formula (10)
724 z_m[0] = 1.0;
725 pz_m[0] = 0.0;
726 z_m[1] = 0.0;
727 pz_m[1] = 1.0;
728 phase_m = 0.0;
729 nxcross_m = 0; // counts the number of crossings of x-axis (excluding first step)
730 nzcross_m = 0;
731 idx = 0; // index for looping over r and pr arrays
732
733 // fill container with initial states
734 y = {init[0],init[1],
735 x_m[0], px_m[0], x_m[1], px_m[1],
736 init[2], init[3],
737 z_m[0], pz_m[0], z_m[1], pz_m[1],
738 phase_m
739 };
740
741 try {
742 // integrate from 0 to 2*pi / nSectors (one has to get back to the "origin")
743 boost::numeric::odeint::integrate_n_steps(stepper_m, orbit_integration,y,0.0,dtheta_m,N_m,store);
744 } catch(OpalException & ex) {
745 *gmsg << ex.where() << " " << ex.what() << endl;
746 break;
747 }
748
749 // write new state
750 x_m[0] = y[2];
751 px_m[0] = y[3];
752 x_m[1] = y[4];
753 px_m[1] = y[5];
754
755 z_m[0] = y[8];
756 pz_m[0] = y[9];
757 z_m[1] = y[10];
758 pz_m[1] = y[11];
759 phase_m = y[12] * Physics::u_two_pi; // / (2.0 * Physics::pi);
760
761 // compute error (compare values of orbit and momentum for theta = 0 and theta = 2*pi)
762 // (Note: size = N_m+1 --> last entry is N_m)
763 err[0] = r_m[N_m] - r_m[0]; // Gordon, formula (14)
764 err[1] = pr_m[N_m] - pr_m[0]; // Gordon, formula (14)
765
766 // correct initial values of r and pr
767 value_type invdenom = 1.0 / (x_m[0] + px_m[1] - 2.0);
768 delta[0] = ((px_m[1] - 1.0) * err[0] - x_m[1] * err[1]) * invdenom; // dr; Gordon, formula (16a)
769 delta[1] = (( x_m[0] - 1.0) * err[1] - px_m[0] * err[0]) * invdenom; // dpr; Gordon, formula (16b)
770
771 // improved initial values; Gordon, formula (17) (here it's used for higher energies)
772 init[0] += delta[0];
773 init[1] += delta[1];
774
775 // compute amplitude of the error (Gordon, formula (18)
776 error = std::sqrt(delta[0] * delta[0] + delta[1] * delta[1] * invgamma4) / r_m[0];
777
778 // *gmsg << "iteration " << niterations << " error: " << error << endl;
779
780 } while ((error > accuracy) && (niterations++ < maxit));
781
782 if (error > accuracy)
783 *gmsg << "findOrbit not converged after " << niterations << " iterations with error: " << error << ". Needed accuracy " << accuracy << endl;
784
785 return (error < accuracy);
786}
787
788template<typename Value_type, typename Size_type, class Stepper>
789Value_type ClosedOrbitFinder<Value_type, Size_type, Stepper>::computeTune(const std::array<value_type,2>& y,
790 value_type py2, size_type ncross)
791{
792 // Y = [y1, y2; py1, py2]
793
794 // cos(mu)
795 value_type cos = 0.5 * (y[0] + py2);
796
797 value_type mu;
798
799 // sign of sin(mu) has to be equal to y2
800 bool negative = std::signbit(y[1]);
801
802 bool uneven = (ncross % 2);
803
804 value_type abscos = std::abs(cos);
805 value_type muPrime;
806 if (abscos > 1.0) {
807 // store the number of crossings
808 if (uneven)
809 ncross = ncross + 1;
810
811 // Gordon, formula (36b)
812 muPrime = -std::acosh(abscos); // mu'
813
814 } else {
815 muPrime = (uneven) ? std::acos(-cos) : std::acos(cos); // mu'
816 /* It has to be fulfilled: 0<= mu' <= pi
817 * But since |cos(mu)| <= 1, we have
818 * -1 <= cos(mu) <= 1 --> 0 <= mu <= pi (using above programmed line), such
819 * that condition is already fulfilled.
820 */
821 }
822
823 // Gordon, formula (36)
824 mu = ncross * Physics::pi + muPrime;
825
826 if (abscos < 1.0) {
827 // if sign(y[1]) > 0 && sign(sin(mu)) < 0
828 if (!negative && std::signbit(std::sin(mu))) {
829 mu = ncross * Physics::pi - muPrime;
830 } else if (negative && !std::signbit(std::sin(mu))) {
831 mu = ncross * Physics::pi - muPrime + Physics::two_pi;
832 }
833 }
834
835 // nu = mu/theta, where theta = integration domain
836
837 /* domain_m = true --> only integrated over a single sector --> multiply by cycl_m->getSymmetry() to
838 * get correct tune.
839 */
840 if (domain_m)
841 mu *= cycl_m->getSymmetry();
842
843 return mu * Physics::u_two_pi;
844}
845
846template<typename Value_type, typename Size_type, class Stepper>
848 /*
849 * The formulas for h, fidx and ds are from the paper:
850 * "Tranverse-Longitudinal Coupling by Space Charge in Cyclotrons"
851 * written by Dr. Christian Baumgarten (2012, PSI)
852 * p. 6
853 */
854
855 // READ IN MAGNETIC FIELD: ONLY FOR STAND-ALONE PROGRAM
856 value_type bint, brint, btint; // B, dB/dr, dB/dtheta
857
858 value_type invbcon = 1.0 / bcon_m(E0_m, wo_m);
859 value_type en = E / E0_m; // en = E/E0 = E/(mc^2) (E0 is potential energy)
860 value_type p = acon_m(wo_m) * std::sqrt(en * (2.0 + en)); // momentum [p] = m; Gordon, formula (3)
861 value_type p2 = p * p;
862 value_type theta = 0.0; // angle for interpolating
863 value_type ptheta;
864
865 // resize of container (--> size = N_m, capacity = N_m)
866 h_m.resize(N_m);
867 fidx_m.resize(N_m);
868 ds_m.resize(N_m);
869
870 for (size_type i = 0; i < N_m; ++i) {
871 // interpolate magnetic field
872 cycl_m->apply(r_m[i], vz_m[i], theta, brint, btint, bint);
873 bint *= invbcon;
874 brint *= invbcon;
875 btint *= invbcon;
876
877 // inverse bending radius
878 h_m[i] = bint / p;
879
880 // local field index
881 if (p < pr_m[i])
882 throw OpalException("ClosedOrbitFinder::computeOrbitProperties()",
883 "p_{r}^2 > p^{2} " + std::to_string(p) + " " + std::to_string(pr_m[i]) + " (defined in Gordon paper) --> Square root of negative number.");
884
885 ptheta = std::sqrt(p2 - pr_m[i] * pr_m[i]);
886
887 fidx_m[i] = (brint * ptheta - btint * pr_m[i] / r_m[i]) / p2; //(bint*bint);
888
889 // path length element
890 ds_m[i] = std::hypot(r_m[i] * pr_m[i] / ptheta,r_m[i]) * dtheta_m; // C++11 function
891
892 // increase angle
893 theta += dtheta_m;
894 }
895
896 // compute average radius
897 ravg_m = std::accumulate(r_m.begin(),r_m.end(),0.0) / value_type(r_m.size());
898}
899
900template<typename Value_type, typename Size_type, class Stepper>
903
904 container_type orbitPropertyCopy = orbitProperty;
905
906 // compute the number of steps per degree
907 value_type deg_step = N_m / 360.0;
908
909 if (angle < 0.0) {
910 angle = 360.0 + angle;
911 }
912
913 // compute starting point
914 unsigned int start = deg_step * angle;
915
916 start %= orbitProperty.size();
917
918 // copy end to start
919 std::copy(orbitProperty.begin() + start, orbitProperty.end(), orbitPropertyCopy.begin());
920
921 // copy start to end
922 std::copy_n(orbitProperty.begin(), start, orbitPropertyCopy.end() - start);
923
924 return orbitPropertyCopy;
925
926}
927
928#endif
Tps< T > cos(const Tps< T > &x)
Cosine.
Definition: TpsMath.h:129
Tps< T > sin(const Tps< T > &x)
Sine.
Definition: TpsMath.h:111
Tps< T > sqrt(const Tps< T > &x)
Square root.
Definition: TpsMath.h:91
T * value_type(const SliceIterator< T > &)
Inform * gmsg
Definition: Main.cpp:61
T::PETE_Expr_t::PETE_Return_t max(const PETE_Expr< T > &expr, NDIndex< D > &loc)
Definition: ReductionLoc.h:84
bool for_each(const BareFieldIterator< T, D > &p, SameFieldID s, C)
Definition: AssignDefs.h:30
const double pi
Definition: fftpack.cpp:894
PETE_TUTree< FnArcCos, typename T::PETE_Expr_t > acos(const PETE_Expr< T > &l)
Definition: PETE.h:725
PETE_TUTree< FnAbs, typename T::PETE_Expr_t > abs(const PETE_Expr< T > &l)
Inform & endl(Inform &inf)
Definition: Inform.cpp:42
Inform & level3(Inform &inf)
Definition: Inform.cpp:47
py::list function(PolynomialPatch *patch, py::list point)
Definition: Air.h:27
constexpr double u_two_pi
The value of.
Definition: Physics.h:36
constexpr double two_pi
The value of.
Definition: Physics.h:33
constexpr double c
The velocity of light in m/s.
Definition: Physics.h:45
constexpr double pi
The value of.
Definition: Physics.h:30
Definition: Units.h:23
constexpr double MHz2Hz
Definition: Units.h:113
FRONT * fs
Definition: hypervolume.cpp:59
std::string getInputBasename()
get input file name without extension
Definition: OpalData.cpp:669
static OpalData * getInstance()
Definition: OpalData.cpp:196
std::string getAuxiliaryOutputDirectory() const
get the name of the the additional data directory
Definition: OpalData.cpp:661
void read(const double &scaleFactor)
Definition: Cyclotron.cpp:773
virtual double getFMHighE() const
Definition: Cyclotron.cpp:365
virtual double getBScale() const
Definition: Cyclotron.cpp:276
virtual double getSymmetry() const
Definition: Cyclotron.cpp:252
virtual double getFMLowE() const
Definition: Cyclotron.cpp:357
std::vector< value_type > container_type
Type of container for storing quantities (path length, orbit, etc.)
std::pair< value_type, value_type > getTunes()
Returns the radial and vertical tunes (in that order)
std::array< value_type, 2 > px_m
Stores current momenta in horizontal direction for the solutions of the ODE with different initial va...
size_type N_m
Number of integration steps.
ClosedOrbitFinder(value_type E0, value_type q, size_type N, Cyclotron *cycl, bool domain=true, int Nsectors=1)
Sets the initial values for the integration and calls findOrbit().
container_type vz_m
Stores the vertical oribt (size: N_m+1)
size_type nzcross_m
Counts the number of zero-line crossings in vertical direction (used for computing vertical tune)
value_type phase_m
Is the phase.
bool findOrbitOfEnergy_m(const value_type &, container_type &, value_type &, const value_type &, size_type)
std::vector< value_type > state_type
Type for holding state of ODE values.
void computeOrbitProperties(const value_type &E)
Fills in the values of h_m, ds_m, fidx_m.
std::array< value_type, 2 > z_m
Stores current position in vertical direction for the solutions of the ODE with different initial val...
container_type fidx_m
Stores the field index.
value_type wo_m
Is the nominal orbital frequency.
container_type ds_m
Stores the step length.
bool findOrbit(value_type accuracy, size_type maxit, value_type ekin, value_type dE=1.0, value_type rguess=-1.0, bool isTuneMode=false)
Computes the closed orbit.
container_type h_m
Stores the inverse bending radius.
std::function< double(double, double)> bcon_m
Cyclotron unit (Tesla)
container_type r_m
Stores the radial orbit (size: N_m+1)
Size_type size_type
Type for specifying sizes.
container_type vpz_m
Stores the vertical momentum.
value_type getAverageRadius()
Returns the average orbit radius.
std::function< double(double)> acon_m
value_type dtheta_m
Is the angle step size.
std::function< void(const state_type &, state_type &, const double)> function_t
container_type getFieldIndex(const value_type &angle=0)
Returns the field index (size of container N+1)
Value_type value_type
Type of variables.
container_type getMomentum(value_type angle=0)
container_type getOrbit(value_type angle=0)
container_type rotate(value_type angle, const container_type &orbitProperty)
This function computes nzcross_ which is used to compute the tune in z-direction and the frequency er...
bool isConverged()
Returns true if a closed orbit could be found.
container_type getPathLength(const value_type &angle=0)
Returns the step lengths of the path (size of container N+1)
std::array< value_type, 2 > pz_m
Stores current momenta in vertical direction for the solutions of the ODE with different initial valu...
std::array< value_type, 2 > x_m
Stores current position in horizontal direction for the solutions of the ODE with different initial v...
value_type ravg_m
Is the average radius.
container_type pr_m
Stores the radial momentum.
container_type getInverseBendingRadius(const value_type &angle=0)
Returns the inverse bending radius (size of container N+1)
value_type getFrequencyError()
Returns the frequency error.
value_type computeTune(const std::array< value_type, 2 > &, value_type, size_type)
This function is called by the function getTunes().
value_type E0_m
Is the rest mass [MeV / c**2].
size_type nxcross_m
Counts the number of zero-line crossings in horizontal direction (used for computing horizontal tune)
Stepper stepper_m
Defines the stepper for integration of the ODE's.
The base class for all OPAL exceptions.
Definition: OpalException.h:28
virtual const std::string & what() const
Return the message string for the exception.
virtual const std::string & where() const
Return the name of the method or function which detected the exception.
Definition: Inform.h:42