OrbitInterpolationToolImpl.java

// @formatter:off
/*******************************************************************************
 * This file is part of JMad. Copyright (c) 2008-2011, CERN. All rights reserved. Licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy
 * of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in
 * writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
 * OF ANY KIND, either express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
// @formatter:on

package cern.accsoft.steering.jmad.tools.interpolate;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import cern.accsoft.steering.jmad.JMadException;
import cern.accsoft.steering.jmad.domain.elem.Element;
import cern.accsoft.steering.jmad.domain.optics.Optic;
import cern.accsoft.steering.jmad.domain.orbit.Orbit;
import cern.accsoft.steering.jmad.domain.types.enums.JMadPlane;
import cern.accsoft.steering.jmad.domain.var.enums.MadxTwissVariable;

public class OrbitInterpolationToolImpl implements OrbitInterpolationTool {

    /** the calculators doing the actual calculation job */
    private List<OrbitSegmentCalculator> calculators = new ArrayList<OrbitSegmentCalculator>();

    /**
     * Create the internal structure for the interpolation. This method assumes,that the list of elements provided
     * describe a complete machine. A calculator for the given plane is created for each region in the machine that is
     * defined by a starting monitor and a ending monitor. If isCircularMachine is set to <code>true</code> the region
     * from the last monitor in the machine sequence to the first one is treated as a region as well. Otherwise there is
     * no interpolation performed before the first and after the last monitor.
     * 
     * @param plane the {@link JMadPlane} for which to create the interpolation structure
     * @param elements the list of {@link Element}s to create the structure from
     * @param monitors the monitors to use for the interpolations
     * @param isCircularMachine set to <code>true</code> if the machine described by the list of elements is a circular
     *            accelerator
     * @return <code>true</code> if the creation of the structure was successful
     */
    private boolean createStructure(JMadPlane plane, List<Element> elements, Set<Element> monitors,
            boolean isCircularMachine) {
        /* clean up */
        this.clearStructure(plane);

        List<Element> lattice = this.sortElementsByPosition(elements);
        List<Element> elementsBeforeFirstMonitor = new ArrayList<Element>();
        Element firstMonitor = null;
        boolean segmentInsertActive = false;
        OrbitSegmentCalculator calculator = null;
        for (Element element : lattice) {
            if (isCircularMachine && !segmentInsertActive && !monitors.contains(element)) {
                /* not a monitor and we are not in a segment yet */
                /* save all the elements for the 'last' calculator */
                elementsBeforeFirstMonitor.add(element);
                continue;
            }

            if (monitors.contains(element)) {
                if (!segmentInsertActive) {
                    /* this is our first monitor */
                    calculator = this.createNewCalculator(plane, element);
                    if (isCircularMachine) {
                        firstMonitor = element;
                    }
                    segmentInsertActive = true;
                    continue;
                } else {
                    /* the end of the current segment */
                    calculator.setEndSegmentMonitor(element);
                    this.calculators.add(calculator);

                    /* and we start with the next segment */
                    calculator = this.createNewCalculator(plane, element);
                    continue;
                }
            }

            if (segmentInsertActive) {
                /* we are in the segment, so just add all elements to the current calculator */
                calculator.addElementToCalculate(element);
            }
        }

        if (isCircularMachine && calculator != null) {
            /* add the elements before the first monitor to the last segment */
            for (Element element : elementsBeforeFirstMonitor) {
                calculator.addElementToCalculate(element);
            }

            /* and end the segment with the first monitor */
            calculator.setEndSegmentMonitor(firstMonitor);
            calculator.setIsCycleStartSegment(true);
            this.calculators.add(calculator);
        }

        return true;
    }

    private OrbitSegmentCalculator createNewCalculator(JMadPlane plane, Element startSegmentMonitor) {
        OrbitSegmentCalculator calculator = new SimpleOrbitSegmentCalculator(plane);
        calculator.setStartSegmentMonitor(startSegmentMonitor);
        return calculator;
    }

    private List<Element> sortElementsByPosition(List<Element> elements) {
        LinkedList<Element> sorted = new LinkedList<Element>();

        for (Element element : elements) {
            if (element.getName().toUpperCase().startsWith("DRIFT")) {
                continue; // XXX maybe there is a better solution, for now just get rid of these!!
            }

            if (!sorted.isEmpty()) {
                Element first = sorted.getFirst();
                Element last = sorted.getLast();

                if (first.getPosition().getValue() > element.getPosition().getValue()) {
                    sorted.addFirst(element);
                    continue;
                }

                /* if same position we add after */
                if (last.getPosition().getValue() <= element.getPosition().getValue()) {
                    sorted.addLast(element);
                    continue;
                }
            } else {
                /* first element so just add */
                sorted.add(element);
                continue;
            }

            int i = 0;
            for (Element sortedElement : sorted) {
                if (element.getPosition().getValue() < sortedElement.getPosition().getValue()) {
                    /* found the first element after the current, replace */
                    break;
                }

                i++;
            }

            if (i > sorted.size()) {
                sorted.addLast(element);
            } else {
                sorted.add(i, element);
            }
        }

        return sorted;
    }

    /**
     * Clear the internal structure used for the orbit interpolation.
     * 
     * @param plane the {@link JMadPlane} for which to clear the interpolation structure.
     */
    private void clearStructure(JMadPlane plane) {
        Iterator<OrbitSegmentCalculator> iterator = this.calculators.iterator();
        while (iterator.hasNext()) {
            if (iterator.next().getPlane() == plane) {
                iterator.remove();
            }
        }
    }

    /**
     * Update the calculators with the given optic.
     * 
     * @param optic the {@link Optic} to use for the update
     * @throws JMadException in case the structure is not yet created, or one of the calculators can not be updated.
     */
    private void updateToOptic(Optic optic) throws JMadException {
        if (this.calculators.size() == 0) {
            throw new JMadException("Could not update to optic, machine structure not defined.");
        }

        for (OrbitSegmentCalculator calculator : this.calculators) {
            if (!calculator.update(optic)) {
                throw new JMadException("Could not update to optic, update failed for " + calculator.getName());
            }
        }
    }

    @Override
    public synchronized OrbitInterpolationResult interpolate(OrbitInterpolationRequest request) throws JMadException {
        Orbit orbit = request.getOrbit();
        if (orbit == null) {
            throw new JMadException("Could not interpolate, no orbit data provided.");
        }

        if (this.calculators.size() == 0) {
            throw new JMadException("Could not interpolate, interpolation tool no properly updated");
        }

        OrbitInterpolationResultImpl result = new OrbitInterpolationResultImpl();
        for (OrbitSegmentCalculator calculator : this.calculators) {
            Map<Element, Map<MadxTwissVariable, Double>> valueMapping = calculator.calculate(request.getOrbit());
            if (valueMapping.size() == 0) {
                throw new JMadException("Could not interpolate, calculation of " + calculator.getName() + " failed");
            }

            result.addValuesPerPlane(calculator.getPlane(), valueMapping);
        }

        result.create();
        return result;
    }

    @Override
    public synchronized void update(UpdateRequest request) throws JMadException {
        for (JMadPlane plane : JMadPlane.values()) {
            if (request.updateStructure(plane)) {
                this.createStructure(plane, request.getMachineElements(), request.getMonitors(plane), request
                        .isCircularMachine());
            }
        }

        if (request.updateMachineOptics()) {
            this.updateToOptic(request.getOptic());
        }
    }
}