Code indexing in gitaly is broken and leads to code not being visible to the user. We work on the issue with highest priority.

Skip to content
Snippets Groups Projects
Forked from OPAL / OPAL-X / src
188 commits behind the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
Ring.cpp 14.92 KiB
/*
 *  Copyright (c) 2012-2014, Chris Rogers
 *  All rights reserved.
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *  3. Neither the name of STFC nor the names of its contributors may be used to
 *     endorse or promote products derived from this software without specific
 *     prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

#include "AbsBeamline/Ring.h"

#include <cmath>
#include <limits>
#include <sstream>

#include "Utility/Inform.h"  // ippl

#include "AbsBeamline/BeamlineVisitor.h"
#include "Fields/EMField.h"
#include "PartBunch/PartBunch.hpp"
#include "Physics/Physics.h"
#include "Structure/LossDataSink.h"

// fairly generous tolerance here... maybe too generous? Maybe should be
// user parameter?
const double Ring::lengthTolerance_m = 1e-2;
const double Ring::angleTolerance_m  = 1e-2;

Ring::Ring(std::string ring)
    : Component(ring),
      planarArcGeometry_m(1, 1),
      refPartBunch_m(nullptr),
      lossDS_m(nullptr),
      beamRInit_m(0.),
      beamPRInit_m(0.),
      beamPhiInit_m(0.),
      latticeRInit_m(0.),
      latticePhiInit_m(0.),
      latticeThetaInit_m(0.),
      isLocked_m(false),
      isClosed_m(true),
      symmetry_m(0),
      cyclHarm_m(0),
      rfFreq_m(0),
      phiStep_m(Physics::pi / 100.),
      ringSections_m(),
      section_list_m() {
    setRefPartBunch(nullptr);
}

void Ring::accept(BeamlineVisitor& visitor) const {
    visitor.visitRing(*this);
}

Ring::Ring(const Ring& ring)
    : Component(ring.getName()),
      planarArcGeometry_m(ring.planarArcGeometry_m),
      lossDS_m(nullptr),
      beamRInit_m(ring.beamRInit_m),
      beamPRInit_m(ring.beamPRInit_m),
      beamPhiInit_m(ring.beamPhiInit_m),
      latticeRInit_m(ring.latticeRInit_m),
      latticePhiInit_m(ring.latticePhiInit_m),
      latticeThetaInit_m(ring.latticeThetaInit_m),
      willDoAperture_m(ring.willDoAperture_m),
      minR2_m(ring.minR2_m),
      maxR2_m(ring.maxR2_m),
      isLocked_m(ring.isLocked_m),
      isClosed_m(ring.isClosed_m),
      symmetry_m(ring.symmetry_m),
      cyclHarm_m(ring.cyclHarm_m),
      phiStep_m(ring.phiStep_m),
      ringSections_m(),
      section_list_m(ring.section_list_m.size()) {
    setRefPartBunch(ring.refPartBunch_m);
    if (ring.lossDS_m != nullptr)
        throw GeneralClassicException(
            "Ring::Ring(const Ring&)",
            "Can't copy construct LossDataSink so copy constructor fails");
    for (size_t i = 0; i < section_list_m.size(); ++i) {
        section_list_m[i] = new RingSection(*ring.section_list_m[i]);
    }
    buildRingSections();
}

Ring::~Ring() {
    for (size_t i = 0; i < section_list_m.size(); ++i)
        delete section_list_m[i];
}

bool Ring::apply(
    const size_t& id, const double& t, Vector_t<double, 3>& E, Vector_t<double, 3>& B) {
    std::shared_ptr<ParticleContainer_t> pc = RefPartBunch_m->getParticleContainer();
    auto Rview                              = pc->R.getView();
    auto Pview                              = pc->P.getView();

    const Vector_t<double, 3> R = Rview(id);
    const Vector_t<double, 3> P = Pview(id);

    bool flagNeedUpdate = apply(R, P, t, E, B);
    if (flagNeedUpdate) {
        Inform gmsgALL("OPAL ", INFORM_ALL_NODES);

        auto Qview   = pc->Q.getView();
        auto Mview   = pc->M.getView();
        auto Binview = pc->Bin.getView();

        const double Q      = Qview(id);
        const double M      = Mview(id);
        //        const short int Bin = Binview(id);

        gmsgALL << getName() << ": particle " << id << " at " << R
                << " m out of the field map boundary" << endl;
        lossDS_m->addParticle(OpalParticle(id, R * Vector_t<double, 3>(1000.0), P, t, Q, M));

        Binview(id) = -1;
    }

    return flagNeedUpdate;
}

bool Ring::apply(
    const Vector_t<double, 3>& R, const Vector_t<double, 3>& /*P*/, const double& t,
    Vector_t<double, 3>& E, Vector_t<double, 3>& B) {
    B = Vector_t<double, 3>({0.0, 0.0, 0.0});
    E = Vector_t<double, 3>({0.0, 0.0, 0.0});

    std::vector<RingSection*> sections =
        getSectionsAt(R);  // I think this doesn't actually use R -DW
    bool outOfBounds = true;
    // assume field maps don't set B, E to 0...
    if (willDoAperture_m) {
        double rSquared = R[0] * R[0] + R[1] * R[1];
        if (rSquared < minR2_m || rSquared > maxR2_m) {
            return true;
        }
    }

    for (size_t i = 0; i < sections.size(); ++i) {
        Vector_t<double, 3> B_temp({0.0, 0.0, 0.0});
        Vector_t<double, 3> E_temp({0.0, 0.0, 0.0});
        // Super-TEMP! cyclotron tracker now uses m internally, have to change to mm here to match
        // old field limits -DW
        outOfBounds &= sections[i]->getFieldValue(
            R * Vector_t<double, 3>(1000.0),
            refPartBunch_m->get_centroid() * Vector_t<double, 3>(1000.0), t, E_temp, B_temp);
        B += (scale_m * B_temp);
        E += (scale_m * E_temp);
    }
    return outOfBounds;
}

void Ring::setLossDataSink(LossDataSink* sink) {
    // lossDS_m is a borrowed pointer - we never delete it
    lossDS_m = sink;
}

void Ring::getDimensions(double& /*zBegin*/, double& /*zEnd*/) const {
    throw GeneralClassicException("Ring::getDimensions", "Cannot get s-dimension of a ring");
}

void Ring::initialise(PartBunch_t* bunch) {
    online_m = true;
    setRefPartBunch(bunch);
    setLossDataSink(new LossDataSink(getName(), false));
}

void Ring::initialise(PartBunch_t* bunch, double& /*startField*/, double& /*endField*/) {
    initialise(bunch);
}

void Ring::finalise() {
    lossDS_m->save();
    online_m = false;
    setLossDataSink(nullptr);
}

void Ring::setRefPartBunch(PartBunch_t* bunch) {
    RefPartBunch_m = bunch;  // inherited from Component
    refPartBunch_m = bunch;  // private data (obeys style guide)
}

std::vector<RingSection*> Ring::getSectionsAt(const Vector_t<double, 3>& /*r*/) {
    return section_list_m;
}

Rotation3D Ring::getRotationStartToEnd(Euclid3D delta) const {
    // rotation from start to end in local coordinates
    // Euclid3D/Rotation3D doesnt have a "getAngle" method so we use fairly
    // obscure technique to extract it.
    Vector3D rotationTest(1., 0., 0.);
    rotationTest               = delta.getRotation() * rotationTest;
    double deltaAngle          = std::atan2(rotationTest(2), rotationTest(0));
    Rotation3D elementRotation = Rotation3D::ZRotation(deltaAngle);
    return elementRotation;
}

void Ring::checkMidplane(Euclid3D delta) const {
    if (std::abs(delta.getVector()(2)) > lengthTolerance_m
        || std::abs(delta.getRotation().getAxis()(0)) > angleTolerance_m
        || std::abs(delta.getRotation().getAxis()(1)) > angleTolerance_m) {
        throw GeneralClassicException(
            "Ring::checkMidplane",
            std::string("Placement of elements out of the ") + "midplane is not supported by Ring");
    }
}

void Ring::rotateToCyclCoordinates(Euclid3D& delta) const {
    Vector3D v = delta.getVector();
    Vector3D r = delta.getRotation().getAxis();
    //    Euclid3D euclid(v(0), -v(2), v(1), r(0), -r(2), r(1));
    Euclid3D euclid(v(2), v(0), -v(1), r(2), r(0), -r(1));
    delta = euclid;
}

Vector_t<double, 3> Ring::getNextPosition() const {
    if (!section_list_m.empty()) {
        return section_list_m.back()->getEndPosition();
    }
    return Vector_t<double, 3>(
        {latticeRInit_m * std::sin(latticePhiInit_m), latticeRInit_m * std::cos(latticePhiInit_m),
         0.});
}

Vector_t<double, 3> Ring::getNextNormal() const {
    if (!section_list_m.empty()) {
        return section_list_m.back()->getEndNormal();
    }
    return Vector_t<double, 3>(
        {std::cos(latticePhiInit_m + latticeThetaInit_m),
         -std::sin(latticePhiInit_m + latticeThetaInit_m), 0.});
}

void Ring::appendElement(const Component& element) {
    if (isLocked_m) {
        throw GeneralClassicException(
            "Ring::appendElement",
            "Attempt to append element " + element.getName() + " when ring is locked");
    }
    // delta is transform from start of bend to end with x, z as horizontal
    // I failed to get Rotation3D to work so use rotations written by hand.
    // Probably an error in my call to Rotation3D.
    Euclid3D delta = element.getGeometry().getTotalTransform();
    rotateToCyclCoordinates(delta);
    checkMidplane(delta);

    RingSection* section          = new RingSection();
    Vector_t<double, 3> startPos  = getNextPosition();
    Vector_t<double, 3> startNorm = getNextNormal();

    section->setComponent(dynamic_cast<Component*>(element.clone()));
    section->setStartPosition(startPos);
    section->setStartNormal(startNorm);

    double startF = std::atan2(startNorm(1), startNorm(0));
    Vector_t<double, 3> endPos =
        Vector_t<double, 3>(
            {+delta.getVector()(0) * std::cos(startF) - delta.getVector()(1) * std::sin(startF),
             +delta.getVector()(0) * std::sin(startF) + delta.getVector()(1) * std::cos(startF), 0})
        + startPos;
    section->setEndPosition(endPos);
    double endF = delta.getRotation().getAxis()(2);  //+
    // atan2(delta.getVector()(1), delta.getVector()(0));
    Vector_t<double, 3> endNorm = Vector_t<double, 3>(
        {+startNorm(0) * std::cos(endF) + startNorm(1) * std::sin(endF),
         -startNorm(0) * std::sin(endF) + startNorm(1) * std::cos(endF), 0});
    section->setEndNormal(endNorm);

    section->setComponentPosition(startPos);
    section->setComponentOrientation(Vector_t<double, 3>({0, 0, startF}));

    section_list_m.push_back(section);

    double dphi = atan2(startNorm(0), startNorm(1));
    Inform msg("OPAL");
    msg << "* Added " << element.getName() << " to Ring" << endl;
    msg << "* Start position (" << section->getStartPosition()(0) << ", "
        << section->getStartPosition()(1) << ", " << section->getStartPosition()(2) << ") normal ("
        << section->getStartNormal()(0) << ", " << section->getStartNormal()(1) << ", "
        << section->getStartNormal()(2) << "), phi " << dphi << endl;
    msg << "* End position   (" << section->getEndPosition()(0) << ", "
        << section->getEndPosition()(1) << ", " << section->getEndPosition()(2) << ") normal ("
        << section->getEndNormal()(0) << ", " << section->getEndNormal()(1) << ", "
        << section->getEndNormal()(2) << ")" << endl;
}

void Ring::lockRing() {
    Inform msg("OPAL ");
    if (isLocked_m) {
        throw GeneralClassicException(
            "Ring::lockRing", "Attempt to lock ring when it is already locked");
    }
    // check for any elements at all
    size_t sectionListSize = section_list_m.size();
    if (sectionListSize == 0) {
        throw GeneralClassicException("Ring::lockRing", "Failed to find any elements in Ring");
    }
    // Apply symmetry properties; I think it is fastest to just duplicate
    // elements rather than try to do some angle manipulations when we do field
    // lookups because we do field lookups in Cartesian coordinates in general.
    msg << "Applying symmetry to Ring" << endl;
    for (int i = 1; i < symmetry_m; ++i) {
        for (size_t j = 0; j < sectionListSize; ++j) {
            appendElement(*section_list_m[j]->getComponent());
        }
    }
    // resetAzimuths();
    //  Check that the ring is closed within tolerance and close exactly
    if (isClosed_m)
        checkAndClose();
    buildRingSections();
    isLocked_m = true;
    for (size_t i = 0; i < section_list_m.size(); i++) {
    }
}

void Ring::resetAzimuths() {
    for (size_t i = 0; i < section_list_m.size(); ++i) {
        Vector_t<double, 3> startPos = section_list_m[i]->getEndPosition();
        Vector_t<double, 3> startDir = section_list_m[i]->getEndNormal();
        Vector_t<double, 3> endPos   = section_list_m[i]->getEndPosition();
        Vector_t<double, 3> endDir   = section_list_m[i]->getEndNormal();
        if (!section_list_m[i]->isOnOrPastStartPlane(endPos)) {
            section_list_m[i]->setEndPosition(startPos);
            section_list_m[i]->setEndNormal(startDir);
            section_list_m[i]->setStartPosition(endPos);
            section_list_m[i]->setStartNormal(endDir);
        }
    }
}
void Ring::checkAndClose() {
    Vector_t<double, 3> first_pos  = section_list_m[0]->getStartPosition();
    Vector_t<double, 3> first_norm = section_list_m[0]->getStartNormal();
    Vector_t<double, 3> last_pos   = section_list_m.back()->getEndPosition();
    Vector_t<double, 3> last_norm  = section_list_m.back()->getEndNormal();
    for (int i = 0; i < 3; ++i) {
        if (std::abs(first_pos(i) - last_pos(i)) > lengthTolerance_m
            || std::abs(first_norm(i) - last_norm(i)) > angleTolerance_m)
            throw GeneralClassicException("Ring::lockRing", "Ring is not closed");
    }
    section_list_m.back()->setEndPosition(first_pos);
    section_list_m.back()->setEndNormal(first_norm);
}

void Ring::buildRingSections() {
    size_t nSections = 2. * Physics::pi / phiStep_m + 1;
    ringSections_m   = std::vector<std::vector<RingSection*> >(nSections);
    for (size_t i = 0; i < ringSections_m.size(); ++i) {
        double phi0 = i * phiStep_m;
        double phi1 = (i + 1) * phiStep_m;
        // std::cerr << phi0 << " " << phi1 << std::endl;
        for (size_t j = 0; j < section_list_m.size(); ++j) {
            if (section_list_m[j]->doesOverlap(phi0, phi1))
                ringSections_m[i].push_back(section_list_m[j]);
        }
    }
}

RingSection* Ring::getLastSectionPlaced() const {
    if (section_list_m.empty()) {
        return nullptr;
    }
    return section_list_m.back();
}

bool Ring::sectionCompare(RingSection const* const sec1, RingSection const* const sec2) {
    return sec1 > sec2;
}

void Ring::setRingAperture(double minR, double maxR) {
    if (minR < 0 || maxR < 0) {
        throw GeneralClassicException(
            "Ring::setRingAperture", "Could not parse negative or undefined aperture limit");
    }

    willDoAperture_m = true;
    minR2_m          = minR * minR;
    maxR2_m          = maxR * maxR;
}