Program Listing for File JF12FieldSolenoidal.cpp

Return to documentation for file (src/magneticField/JF12FieldSolenoidal.cpp)

#include "crpropa/magneticField/JF12FieldSolenoidal.h"
#include "crpropa/Units.h"
#include "crpropa/GridTools.h"
#include "crpropa/Random.h"

namespace crpropa {

JF12FieldSolenoidal::JF12FieldSolenoidal(double delta, double zs) {
        zS = zs; // set scale heigth for the parabolic X field lines
        r1 = 5 * kpc; // inner boundary of the disk field
        r2 = 20 * kpc; // outer boudary of the disk field
        r1s = r1 + delta; // the magnetic flux of the spirals is redirected for r in [r1,r1s]
        r2s = r2 - delta; // same here at outer boundary between r2s and r2
        phi0 = 0.; // somewhat arbitrary choice, has to be chosen in [-pi,pi]

        for (int i = 1;i < 9; i++){
                // fill the array with angles in [-pi,pi] where the 8 spiral arms intersect the r1 - ring
                // indexing starts at 1 to match the indexing in the papers on the JF12 field!
                phi0Arms[i] = M_PI - cotPitch * log(rArms[i-1] / r1);
        }

        // cyclic closure of the array, with next values periodically continued
        // outside [-pi,pi] to simplify looping and searching for correct spiral arms
        phi0Arms[0] = phi0Arms[8] + 2 * M_PI;
        phi0Arms[9] = phi0Arms[1] - 2 * M_PI;
        phi0Arms[10] = phi0Arms[2] - 2 *M_PI;

        // determine the position of phi0 in the array, i.e. find the correct spiral arm.
        int idx0 = 1; // corresponding index in phi0Arms such that phi0Arms[idx0] < phi0 < phi0Arms[idx0-1]
        while (phi0 < phi0Arms[idx0]){
                idx0 += 1; // search clockwise, starting with the check if phi0Arms[1] < phi0 < phi0Arms[0]
        }

        // fill the bDisk array with spiral field strengths at r = r1.
        // note the indexing starting with 1 here to match the indexing in the JF12 papers!
        // for a position (r1,phi), phi in [-pi,pi], the correct field strength is given by
        // bDisk[i] if phi0Arms[i] < phi0 < phi0Arms[i-1].
        bDisk[1] = 0.1 * muG;
        bDisk[2] = 3.0 * muG;
        bDisk[3] = -0.9 * muG;
        bDisk[4] = -0.8 * muG;
        bDisk[5] = -2.0 * muG;
        bDisk[6] = -4.2 * muG;
        bDisk[7] = 0.0 * muG;

        // re-compute b_8 for actual (net flux = 0)-correction of the spiral field with minimal round-off errors
        double flux1to7 = 0.;
        for (int i = 1; i < 8; i++){
                flux1to7 += (phi0Arms[i-1] - phi0Arms[i]) * bDisk[i];
        }
        bDisk[8] = -flux1to7 / (phi0Arms[7] - phi0Arms[8]);

        bDisk[0] = bDisk[8]; // again close the array periodically
        bDisk[9] = bDisk[1];
        bDisk[10] = bDisk[2];

        // set coefficients for the evaluation of the phi-integral over the piecewise constant field strengths at r=r1
        // such that it may be evaluated as H(phi) = phiCoeff[j] + bDisk[j] * phi later on
        // start integration at phi0Arms[0] first, shift to lower integration boundary phi0 later
        phiCoeff[0] = 0;
        for (int i = 1; i < 10; i++){
                phiCoeff[i] = phiCoeff[i-1] + (bDisk[i-1] - bDisk[i]) * phi0Arms[i-1];
        }

        // correct for H(phi0) = 0
        corr = phiCoeff[idx0] + bDisk[idx0] * phi0;
        for (int i = 1; i < 10; i++){
                phiCoeff[i] = phiCoeff[i] - corr;
        }
}

void JF12FieldSolenoidal::setDiskTransitionWidth(double delta) {
        r1s = r1 + delta;
        r2s = r2 - delta;
}

void JF12FieldSolenoidal::setXScaleHeight(double zs) {
        zS = zs;
}

double JF12FieldSolenoidal::getDiskTransitionWidth() const {
        return (r1s - r1);
}

double JF12FieldSolenoidal::getXScaleHeight() const {
        return zS;
}

void JF12FieldSolenoidal::deactivateOuterTransition() {
        r2s = r2;
}

void JF12FieldSolenoidal::setUseStriatedField(bool use) {
        if ((use) and (striatedGrid)) {
                KISS_LOG_WARNING << "JF12FieldSolenoidal: No striated field set: ignored.";
                return;
        }
        useStriatedField = use;
}

void JF12FieldSolenoidal::setUseTurbulentField(bool use) {
        if ((use) and (turbulentGrid)) {
                KISS_LOG_WARNING << "JF12FieldSolenoidal: No turbulent field set: ignored.";
                return;
        }
        useTurbulentField = use;
}

Vector3d JF12FieldSolenoidal::getDiskField(const double& r, const double& z, const double& phi, const double& sinPhi, const double& cosPhi) const {
        Vector3d b(0.);

        if (useDiskField){
                double lfDisk = logisticFunction(z, hDisk, wDisk); // for vertical scaling as in initial JF12

                double hint = getHPhiIntegral(r, phi); // phi integral to restore solenoidality in transition region, only enters if r is in [r1,r1s] or [r2s,r2]
                double mag1 = getSpiralFieldStrengthConstant(r, phi); // returns bDisk[j] for the current spiral arm

                if ((r1 < r) && (r < r2)) {
                        double pdelta = getDiskTransitionPolynomial(r);
                        double qdelta = getDiskTransitionPolynomialDerivative(r);
                        double br = pdelta * mag1 * sinPitch;
                        double bphi = pdelta * mag1 * cosPitch - qdelta * hint * sinPitch;

                        b.x += br * cosPhi - bphi * sinPhi;
                        b.y += br * sinPhi + bphi * cosPhi;

                        b *= (1 - lfDisk);
                }
        }
        return b;
}

Vector3d JF12FieldSolenoidal::getXField(const double& r, const double& z, const double& sinPhi, const double& cosPhi) const {
        Vector3d b(0.);

        if (useXField){
                double bMagX;
                double sinThetaX, cosThetaX;
                double rp; // radius where current intial field line passes z = 0
                double rc = rXc + fabs(z) / tanThetaX0;
                double r0c = rXc + zS / tanThetaX0; // radius where field line through rXc passes z = zS
                double f, r0, br0, bz0;
                bool inner = true; // distinguish between inner and outer region

                // return intial field if z>=zS
                if (fabs(z) > zS){
                        if ((r == 0.)){
                                b.z = bX / ((1. + fabs(z) * cotThetaX0 / rXc) * (1. + fabs(z) * cotThetaX0 / rXc));
                                return b;
                        }

                        if (r < rc) {
                        // inner varying elevation region
                                rp = r * rXc / rc;
                                bMagX = bX * exp(-1 * rp / rX) * (rXc / rc) * (rXc / rc);

                                double thetaX = atan(fabs(z) / (r - rp));

                                if (z == 0)
                                        thetaX = M_PI / 2.;

                                sinThetaX = sin(thetaX);
                                cosThetaX = cos(thetaX);
                        }
                        else {
                        // outer constant elevation region
                                rp = r - fabs(z) / tanThetaX0;
                                bMagX = bX * exp(-rp / rX) * (rp / r);

                                sinThetaX = sinThetaX0;
                                cosThetaX = cosThetaX0;
                        }
                        double zsign = z < 0 ? -1 : 1;
                        b.x += zsign * bMagX * cosThetaX * cosPhi;
                        b.y += zsign * bMagX * cosThetaX * sinPhi;
                        b.z += bMagX * sinThetaX;
                }
                // parabolic field lines for z<zS
                else {
                                // determine r at which parabolic field line through (r,z) passes z = zS
                                r0 = r * 1. / (1.- 1./ (2. * (zS + rXc * tanThetaX0)) * (zS - z * z / zS));

                                // determine correct region (inner/outer)
                                // and compute factor F for solenoidality
                                if (r0 >= r0c){
                                        r0 = r + 1. / (2. * tanThetaX0) * (zS - z * z / zS);
                                        f = 1. + 1/ (2 * r * tanThetaX0/ zS) * (1. - (z / zS) * (z / zS));
                                }
                                else
                                {
                                         f = 1. / ((1. - 1./( 2. + 2. * (rXc * tanThetaX0/ zS)) * (1. - (z / zS) * (z / zS))) * (1. - 1./( 2. + 2. * (rXc * tanThetaX0/ zS)) * (1. - (z / zS) * (z / zS))));
                                }

                                // field strength at that position
                                if (r0 < r0c){
                                         rp = r0 * rXc / r0c;
                                         double thetaX = atan(zS / (r0 - rp));

                                         // field strength at (r0,zS) for inner region
                                         br0 = bX * exp(- rp / rX) * (rXc/ r0c) * (rXc/ r0c) * cos(thetaX);
                                         bz0 = bX * exp(- rp / rX) * (rXc/ r0c) * (rXc/ r0c) * sin(thetaX);
                                 }
                                 else {
                                         // field strength at (r0,zS) for outer region
                                         rp = r0 - zS / tanThetaX0;
                                         br0 =  bX * exp(- rp / rX) * (rp/r0) * cosThetaX0;
                                         bz0 =  bX * exp(- rp / rX) * (rp/r0) * sinThetaX0;
                                 }

                                 double br = z / zS * f * br0;
                                 double bz = bz0 * f;

                                 b.x += br * cosPhi;
                                 b.y += br * sinPhi;
                                 b.z += bz;
                }
        }
        return b;
}

double JF12FieldSolenoidal::getDiskTransitionPolynomial(const double& r) const {
        // 0 disk field outside
        if ((r < r1) || (r > r2)) {
                return 0.;
        }
        // unchanged field
        if ((r > r1s) && (r < r2s)) {
                return r1/r;
        }
        // transitions region parameters
        double r_a = r1;
        double r_b = r1s;

        if (r >= r2s) {
                r_a = r2;
                r_b = r2s;
        }
        // differentiable transition at r_s, continous at r_a
        double fakt = (r_a / r_b - 2.) / ((r_a - r_b) *  (r_a - r_b));
        return (r1/r_b) * (2. - r / r_b + fakt * (r-r_b) * (r-r_b));
}

double JF12FieldSolenoidal::getDiskTransitionPolynomialDerivative(const double& r) const {
        // 0 disk field outside
        if ((r < r1) || (r > r2)) {
                return 0.;
        }
        // unchanged field
        if ((r > r1s) && (r < r2s)) {
                return 0.;
        }
        // transitions region parameters
        double r_a = r1;
        double r_b = r1s;

        if (r >= r2s) {
                r_a = r2;
                r_b = r2s;
        }
        // differentiable transition polynomial at r_s, continous at r_a
        double fakt = (r_a / r_b - 2.) / ((r_a - r_b) * (r_a - r_b));
        return (r1/r_b) * (2. - 2. * r/r_b + fakt * (3. * r * r - 4. * r * r_b + r_b * r_b));
}

double JF12FieldSolenoidal::getHPhiIntegral(const double& r, const double& phi) const {
        // Evaluates the H(phi1) integral for solenoidality for the position (r,phi) which is mapped back to (r1=5kpc,phi1)
        // along the spiral field line.
        double H_ret = 0.;

        if ((r1 < r) && (r < r2)){
                // find index of the correct spiral arm for (r1,phi1) just like in getSpiralFieldStrengthConstant
                int idx = 1;
                double phi1 = phi - log(r/r1) * cotPitch;
                phi1 = atan2(sin(phi1), cos(phi1));
                while (phi1 < phi0Arms[idx]){
                        idx += 1;
                }
                H_ret = phi1 * bDisk[idx] + phiCoeff[idx];
        }
        return H_ret;
}

double JF12FieldSolenoidal::getSpiralFieldStrengthConstant(const double& r, const double& phi) const {
        // For a given position (r, phi) in polar coordinates, this method returns the field strength
        // of the spiral field at r1 = 5 kpc for the magnetic spiral arm where (r, phi) is located.
        // The method first computes the angle phi1 at which the spiral field line passing through (r, phi) intersects
        // the circle with radius r1 = 5 kpc. Afterwards, the correct spiral arm is found by searching the index idx
        // such that phi0Arms[idx] < phi1 < phi0Arms[idx-1]. The correct field strength of the respective spiral arm
        // where (r, phi) is located is then given as bDisk[idx].
        double b_ret = 0.;
        int idx = 1;
        if ((r1 < r) && (r < r2)){
                double phi1 = phi - log(r/r1) * cotPitch; // map the position (r, phi) to (5 kpc, phi1) along the logarithmic spiral field line
                phi1 = atan2(sin(phi1), cos(phi1)); // map this angle to [-pi,+pi]
                while (phi1 < phi0Arms[idx]){
                        idx += 1; // run clockwise through the spiral arms; the cyclic closure of phi0Arms[9] = phi0Arms[1] - 2 pi is needed if -pi <= phi1 <= phi0Arms[8].
                }
                b_ret = bDisk[idx];
        }
        return b_ret;
}
} // namespace crpropa