JMadModelImpl.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.model;

import static java.util.Collections.singletonList;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import cern.accsoft.steering.jmad.JMadException;
import cern.accsoft.steering.jmad.domain.aperture.Aperture;
import cern.accsoft.steering.jmad.domain.beam.Beam;
import cern.accsoft.steering.jmad.domain.elem.Element;
import cern.accsoft.steering.jmad.domain.elem.ElementAttributeReader;
import cern.accsoft.steering.jmad.domain.elem.ElementListener;
import cern.accsoft.steering.jmad.domain.elem.MadxElementType;
import cern.accsoft.steering.jmad.domain.elem.impl.AbstractElement;
import cern.accsoft.steering.jmad.domain.elem.impl.ElementFactory;
import cern.accsoft.steering.jmad.domain.ex.JMadModelException;
import cern.accsoft.steering.jmad.domain.file.CallableModelFile;
import cern.accsoft.steering.jmad.domain.file.CallableModelFile.ParseType;
import cern.accsoft.steering.jmad.domain.file.ModelFile;
import cern.accsoft.steering.jmad.domain.file.TableModelFile;
import cern.accsoft.steering.jmad.domain.knob.AbstractKnob;
import cern.accsoft.steering.jmad.domain.knob.Knob;
import cern.accsoft.steering.jmad.domain.knob.KnobListener;
import cern.accsoft.steering.jmad.domain.knob.attribute.ElementAttribute;
import cern.accsoft.steering.jmad.domain.knob.strength.SimpleStrength;
import cern.accsoft.steering.jmad.domain.knob.strength.Strength;
import cern.accsoft.steering.jmad.domain.machine.ApertureDefinition;
import cern.accsoft.steering.jmad.domain.machine.Range;
import cern.accsoft.steering.jmad.domain.machine.RangeDefinition;
import cern.accsoft.steering.jmad.domain.machine.RangeListener;
import cern.accsoft.steering.jmad.domain.machine.SequenceDefinition;
import cern.accsoft.steering.jmad.domain.misalign.MisalignmentConfiguration;
import cern.accsoft.steering.jmad.domain.optics.Optic;
import cern.accsoft.steering.jmad.domain.optics.OpticImpl;
import cern.accsoft.steering.jmad.domain.result.Result;
import cern.accsoft.steering.jmad.domain.result.ResultType;
import cern.accsoft.steering.jmad.domain.result.StrengthResult;
import cern.accsoft.steering.jmad.domain.result.match.MatchResult;
import cern.accsoft.steering.jmad.domain.result.match.MatchResultImpl;
import cern.accsoft.steering.jmad.domain.result.match.MatchResultRequest;
import cern.accsoft.steering.jmad.domain.result.match.input.MadxVaryParameter;
import cern.accsoft.steering.jmad.domain.result.match.input.MatchConstraint;
import cern.accsoft.steering.jmad.domain.result.match.input.MatchConstraintLocal;
import cern.accsoft.steering.jmad.domain.result.match.output.MadxVaryResult;
import cern.accsoft.steering.jmad.domain.result.match.output.MatchConstraintResultGlobal;
import cern.accsoft.steering.jmad.domain.result.match.output.MatchConstraintResultLocal;
import cern.accsoft.steering.jmad.domain.result.tfs.TfsResult;
import cern.accsoft.steering.jmad.domain.result.tfs.TfsResultRequest;
import cern.accsoft.steering.jmad.domain.result.tfs.TfsResultRequestImpl;
import cern.accsoft.steering.jmad.domain.result.tfs.TfsSummary;
import cern.accsoft.steering.jmad.domain.result.track.DynapResult;
import cern.accsoft.steering.jmad.domain.result.track.DynapResultRequest;
import cern.accsoft.steering.jmad.domain.result.track.TrackResult;
import cern.accsoft.steering.jmad.domain.result.track.TrackResultRequest;
import cern.accsoft.steering.jmad.domain.track.TrackInitialCondition;
import cern.accsoft.steering.jmad.domain.twiss.TwissInitialConditions;
import cern.accsoft.steering.jmad.domain.twiss.TwissInitialConditionsImpl;
import cern.accsoft.steering.jmad.domain.twiss.TwissListener;
import cern.accsoft.steering.jmad.domain.var.custom.StrengthVarSet;
import cern.accsoft.steering.jmad.domain.var.enums.EalignVariables;
import cern.accsoft.steering.jmad.domain.var.enums.MadxGlobalVariable;
import cern.accsoft.steering.jmad.domain.var.enums.MadxTwissVariable;
import cern.accsoft.steering.jmad.io.ApertureReader;
import cern.accsoft.steering.jmad.io.ApertureReaderImpl;
import cern.accsoft.steering.jmad.kernel.JMadKernel;
import cern.accsoft.steering.jmad.kernel.JMadKernelImpl;
import cern.accsoft.steering.jmad.kernel.MadxTerminatedException;
import cern.accsoft.steering.jmad.kernel.cmd.CallCommand;
import cern.accsoft.steering.jmad.kernel.cmd.Command;
import cern.accsoft.steering.jmad.kernel.cmd.EOptionCommand;
import cern.accsoft.steering.jmad.kernel.cmd.FreeText;
import cern.accsoft.steering.jmad.kernel.cmd.SaveBetaCommand;
import cern.accsoft.steering.jmad.kernel.cmd.SetEqual;
import cern.accsoft.steering.jmad.kernel.cmd.SetListEqual;
import cern.accsoft.steering.jmad.kernel.cmd.UseCommand;
import cern.accsoft.steering.jmad.kernel.cmd.ptc.PtcEndCommand;
import cern.accsoft.steering.jmad.kernel.cmd.table.ReadMyTableCommand;
import cern.accsoft.steering.jmad.kernel.cmd.table.ReadTableCommand;
import cern.accsoft.steering.jmad.kernel.task.CycleSequence;
import cern.accsoft.steering.jmad.kernel.task.GetMisalignmentsTask;
import cern.accsoft.steering.jmad.kernel.task.GetValues;
import cern.accsoft.steering.jmad.kernel.task.RunMatch;
import cern.accsoft.steering.jmad.kernel.task.RunTwiss;
import cern.accsoft.steering.jmad.kernel.task.SetBeam;
import cern.accsoft.steering.jmad.kernel.task.SetMisalignmentsTask;
import cern.accsoft.steering.jmad.kernel.task.ptc.InitPtcTask;
import cern.accsoft.steering.jmad.kernel.task.ptc.RunPtcTwiss;
import cern.accsoft.steering.jmad.kernel.task.track.DynapTask;
import cern.accsoft.steering.jmad.kernel.task.track.TrackTask;
import cern.accsoft.steering.jmad.model.manage.StrengthVarManager;
import cern.accsoft.steering.jmad.modeldefs.domain.JMadModelDefinition;
import cern.accsoft.steering.jmad.modeldefs.domain.OpticsDefinition;
import cern.accsoft.steering.jmad.modeldefs.io.ModelFileFinder;
import cern.accsoft.steering.jmad.modeldefs.io.ModelFileFinderManager;
import cern.accsoft.steering.jmad.util.FileUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JMadModelImpl implements JMadModel, ElementAttributeReader {

    /**
     * The logger for the class
     */
    protected static final Logger LOGGER = LoggerFactory.getLogger(JMadModelImpl.class);

    /**
     * The definition of the model
     */
    private JMadModelDefinition modelDefinition = null;

    /**
     * The last loaded optics definition
     */
    private OpticsDefinition activeOpticsDefinition = null;

    /**
     * The last loaded range definition
     */
    private RangeDefinition activeRangeDefinition = null;

    /**
     * The active Range
     */
    private Range activeRange = null;

    /**
     * The actually loaded aperture data. Might be null.
     */
    private Aperture aperture = null;

    /**
     * The kernel for this model to be injected by spring
     */
    private JMadKernel kernel;

    /**
     * The last calculated optics-values
     */
    private Optic optics = new OpticImpl();

    /**
     * The startup configuration defines which optics are loaded per default
     */
    private JMadModelStartupConfiguration startupConfiguration = new JMadModelStartupConfiguration();

    /**
     * The Manager which keeps all model-specific knobs
     */
    private final KnobManager knobManager;

    /**
     * The file finder for model files to be injected by spring
     */
    private ModelFileFinderManager modelFileFinderManager;

    /**
     * The listeners
     */
    private final List<JMadModelListener> listeners = new ArrayList<>();

    /**
     * Keeps track of all the strengths and variables
     */
    private StrengthVarManager strengthVarManager;

    /**
     * true, if some strengthes have been set since last recalc of optics
     */
    protected boolean dirtyModel = false;

    /**
     * the twiss initial conditions. Shall also be present, if no active range is selected. Therefore they are set to
     * empty ones by default.
     */
    private TwissInitialConditions twissInitialConditions = new TwissInitialConditionsImpl();

    /**
     * The mode defines which kind of twiss is used
     */
    private ModelMode modelMode = ModelMode.MADX;

    /**
     * The listener, which is added to all elemnts in order to update the madx-instance correctly, when element values
     * change.
     */
    private final ElementListener elementListener = new ElementListener() {
        @Override
        public void changedAttribute(Element element, String attributeName) {
            String name = element.getName() + Element.ATTR_SEPARATOR + attributeName;
            try {
                setValue(name, element.getAttribute(attributeName));
            } catch (JMadModelException e) {
                LOGGER.error("Error while passing new element-attribute value '" + name + "' to MadX.", e);
            }
        }
    };

    /**
     * The listener which is added to all strengths in order to update the madx-instance, when one of the element values
     * chages.
     */
    private final KnobListener strengthListener = new KnobListener() {
        @Override
        public void changedValue(Knob strength) {
            try {
                set(strength);
            } catch (JMadModelException e) {
                LOGGER.error("Error while passing new value for strength '" + strength.getName() + "' to MadX.", e);
            }
        }
    };

    /**
     * the listener to be notified when the twiss-initial conditions are changed.
     */
    private final TwissListener twissListener = new TwissListener() {

        /**
         * @param twiss
         */
        @Override
        public void changedTwiss(TwissInitialConditions twiss) {
            dirtyModel = true;
            fireBecameDirty();
        }
    };

    public JMadModelImpl() {
        twissInitialConditions.addListener(this.twissListener);
        this.knobManager = new KnobManagerImpl(this);
    }

    /**
     * ensures, that the model is initialized.
     *
     * @throws JMadModelException if the initialization fails
     */
    private void ensureInit() throws JMadModelException {
        if (!isInitialized()) {
            LOGGER.debug("Model is not yet initialized. Starting init.");
            init();
        }
    }

    public void setModelDefinition(JMadModelDefinition modelDefinition) {
        this.modelDefinition = modelDefinition;
    }

    @Override
    public void init() throws JMadModelException {
        LOGGER.debug("initializing model.");

        /*
         * first we have to start the kernel
         */
        try {
            getKernel().start();
        } catch (JMadException e) {
            throw new JMadModelException("Error while initializing model.", e);
        }

        /*
         * if we have no model definition then we are done
         */
        if (getModelDefinition() == null) {
            return;
        }

        /*
         * If we have a model definition then we first of all run all the init files.
         */
        processModelFiles(getModelDefinition().getInitFiles());

        /* load the available beams for later use to the model */
        for (SequenceDefinition sequenceDefinition : getModelDefinition().getSequenceDefinitions()) {
            Beam beam = sequenceDefinition.getBeam();

            if (beam != null) {
                /* set sequence in beam for resbeam command */
                beam.setSequence(sequenceDefinition.getName());
                try {
                    getKernel().execute(new SetBeam(beam));
                } catch (JMadException e) {
                    throw new JMadModelException("Error defining beam for sequence [" + sequenceDefinition.getName()
                            + "] during model initialization.", e);
                }
            }
        }

        /*
         * then we decide if we should load some default ranges and optics.
         */

        OpticsDefinition opticsDefinition = getStartupConfiguration().getInitialOpticsDefinition();
        if (opticsDefinition == null) {
            opticsDefinition = getModelDefinition().getDefaultOpticsDefinition();
        }
        if (opticsDefinition == null) {
            throw new JMadModelException(
                    "Neither a initial optic (in the startup configuration), nor a default optics is defined."
                            + "Cannot correctly initialize the model.");
        }
        setActiveOpticsDefinition(opticsDefinition);

        RangeDefinition rangeDefinition = getStartupConfiguration().getInitialRangeDefinition();
        if ((rangeDefinition == null) && getStartupConfiguration().isLoadDefaultRange()) {
            rangeDefinition = getModelDefinition().getDefaultRangeDefinition();
        }
        if (rangeDefinition != null) {
            setActiveRangeDefinition(rangeDefinition);
        }
        calcOptics();
    }

    private void processModelFiles(List<ModelFile> modelFiles) {
        boolean containsStrengthFile = false;
        for (ModelFile modelFile : modelFiles) {
            if ((modelFile instanceof CallableModelFile) && (ParseType.STRENGTHS == ((CallableModelFile) modelFile)
                    .getParseType())) {
                containsStrengthFile = true;
                break;
            }
        }

        if (containsStrengthFile) {
            for (Strength strength : strengthVarManager.getStrengthVarSet().getStrengths()) {
                strength.removeListener(this.strengthListener);
            }
        }

        for (ModelFile modelFile : modelFiles) {
            File file = getModelFileFinder().getFile(modelFile, getKernel());
            if (modelFile instanceof CallableModelFile) {
                this.call(file);
                if (ParseType.STRENGTHS == ((CallableModelFile) modelFile).getParseType()) {
                    strengthVarManager.load(file);
                }
            } else if (modelFile instanceof TableModelFile) {
                this.readTable(file, ((TableModelFile) modelFile).getTableName());
            } else {
                LOGGER.error("Do not know what to do with modelFile of class '" + modelFile.getClass().getName() + "'");
            }
        }
        if (containsStrengthFile) {
            for (Strength strength : this.strengthVarManager.getStrengthVarSet().getStrengths()) {
                strength.addListener(this.strengthListener);
            }
        }
    }

    /**
     * instructs madx to read a table from a file.
     * <p>
     * If a tableName is given, then the {@link ReadMyTableCommand} is used and the table is stored in the given table
     * name. If the name is <code>null</code>, then the tablename must be given in the file.
     *
     * @param file      the file from which to load the table
     * @param tableName the name of the table (lowercase!)
     */
    public void readTable(File file, String tableName) {
        Command cmd;
        if (tableName == null) {
            cmd = new ReadTableCommand(file);
        } else {
            cmd = new ReadMyTableCommand(file, tableName);
        }
        try {
            this.kernel.execute(cmd);
        } catch (JMadException e) {
            LOGGER.error("Could not load table '" + file.getAbsolutePath() + "'");
        }
    }

    @Override
    public void reset() throws JMadModelException {
        cleanup();
        init();
    }

    /**
     * @return true if the model is initialized, false otherwise.
     */
    @Override
    public boolean isInitialized() {
        /*
         * is satisfying at the moment, might be refined sometime
         */
        return getKernel().isMadxRunning();
    }

    @Override
    public void cleanup() throws JMadModelException {
        try {
            if (getKernel().isMadxRunning()) {
                getKernel().stop();
            }
        } catch (JMadException e) {
            throw new JMadModelException("Error while stopping MadX-Kernel.", e);
        }

        this.activeOpticsDefinition = null;
        this.activeRangeDefinition = null;
        this.activeRange = null;

        optics = new OpticImpl();
        this.knobManager.cleanup();
    }

    @Override
    public synchronized JMadKernel getKernel() {
        return this.kernel;
    }

    @Override
    public List<Double> getValues(List<String> valueNames) throws JMadModelException {
        return this.getValuesResult(valueNames).getDoubleValues();
    }

    @Override
    public Map<String, Double> getValueMap(Collection<String> valueNames) throws JMadModelException {
        Map<String, Double> mapping = new HashMap<>();
        for (Strength strength : this.getValuesResult(valueNames).getValues()) {
            mapping.put(strength.getMadxName(), strength.getTotalValue());
        }
        return mapping;
    }

    private StrengthResult getValuesResult(Collection<String> valueNames) throws JMadModelException {

        ensureInit();

        StrengthResult valuesResult = null;

        try {
            Result result = getKernel().execute(new GetValues(valueNames));
            if (ResultType.VALUES_RESULT != result.getResultType()) {
                throw new JMadModelException("GetValues returned wrong type of result!");
            }
            valuesResult = (StrengthResult) result;
        } catch (JMadException e) {
            throw new JMadModelException("Error executing getValues.", e);
        }
        return valuesResult;
    }

    @Override
    public double getValue(String valueName) throws JMadModelException {
        List<Double> values = getValues(singletonList(valueName));
        if (values.size() != 1) {
            throw new JMadModelException("Result contains " + values.size() + " values, but should contain one.");
        }
        return values.get(0);
    }

    /**
     * calculates the actual optics-values and stores them in the internal Optics-object. This can be retrieved by
     * <code>getOptics()</code>
     *
     * @return the optics-object
     * @throws JMadModelException if the calculation of the optics fails
     */
    private Optic calcOptics() throws JMadModelException {
        if (getActiveRange() == null) {
            return optics;
        }

        this.optics = OpticUtil.calcOptic(this);
        dirtyModel = false;
        fireOpticsChanged();
        return optics;
    }

    /**
     * creates all the element-objects for the active range by twissing and reading in the names and types of the
     * elements.
     *
     * @throws JMadModelException if the reading of the active range fails
     */
    private void readActiveRange() throws JMadModelException {
        activeRange.clear();
        this.knobManager.cleanup();

        TfsResultRequestImpl resultRequest = new TfsResultRequestImpl();

        /* request all elements */
        resultRequest.addElementFilter(".*");

        /* request name, type and position. */
        resultRequest.addVariable(MadxTwissVariable.NAME);
        resultRequest.addVariable(MadxTwissVariable.KEYWORD);
        resultRequest.addVariable(MadxTwissVariable.S);

        TfsResult tfsResult = twiss(resultRequest);
        List<String> names = tfsResult.getStringData(MadxTwissVariable.NAME);
        List<String> keywords = tfsResult.getStringData(MadxTwissVariable.KEYWORD);
        List<Double> positions = tfsResult.getDoubleData(MadxTwissVariable.S);

        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            String keyword = keywords.get(i);
            MadxElementType madxElementType = MadxElementType.fromMadXName(keyword);
            Element element = ElementFactory.createElement(madxElementType, name);
            if (element != null) {
                if (element instanceof AbstractElement) {
                    ((AbstractElement) element).setAttributesReader(this);
                }
                element.setPosition(positions.get(i));
                activeRange.add(element);
                /* finally we register this model as listener */
                element.addListener(this.elementListener);
            }
        }
        dirtyModel = true;
        fireBecameDirty();
        fireElementsChanged();
    }

    @Override
    public void readAttributes(Element element) throws JMadModelException {
        readAttributes(singletonList(element));
    }

    @Override
    public void readAttributes(Collection<Element> elements) throws JMadModelException {
        ensureInit();

        /*
         * first of all we deactivate the listeners.
         */
        for (Element element : elements) {
            element.setListenersEnabled(false);
            element.setAttributesInitialized(true); /* this has to be set here to prevent call loops */
        }

        ArrayList<String> valueNames = new ArrayList<>();

        for (Element element : elements) {
            List<String> attributeNames = element.getAttributeNames();

            for (String attributeName : attributeNames) {
                valueNames.add(element.getName() + Element.ATTR_SEPARATOR + attributeName);
            }
        }

        List<Double> values = getValues(valueNames);

        if (values.size() != valueNames.size()) {
            throw new JMadModelException(
                    "Amount of returned Values (" + values.size() + ") differs from requested ones (" + valueNames
                            .size() + ").");
        }

        int index = 0;
        for (Element element : elements) {
            List<String> attributeNames = element.getAttributeNames();
            for (String attributeName : attributeNames) {
                element.setAttribute(attributeName, values.get(index));
                index++;
            }
        }

        /* finally we re-activate the listeners */
        for (Element element : elements) {
            element.setListenersEnabled(true);
        }
    }

    /**
     * sends the misalignment-commands to madx
     *
     * @param misalignmentConfigurations the misalignment-configurations from which to create the commands
     */
    protected void setMisalignments(List<MisalignmentConfiguration> misalignmentConfigurations) {

        if (getKernel().isMadxRunning()) {
            try {
                getKernel().execute(new SetMisalignmentsTask(misalignmentConfigurations));
            } catch (JMadException e) {
                LOGGER.error("Error while setting Misalignment.", e);
            }
        }
        dirtyModel = true;
        fireBecameDirty();
    }

    //
    // twiss methods
    //

    @Override
    public TfsResult twiss(TfsResultRequest resultRequest) throws JMadModelException {
        return this.twiss(resultRequest, getTwissInitialConditions());
    }

    @Override
    public TfsResult twissToFile(TfsResultRequest resultRequest, File tfsFile) throws JMadModelException {
        return this.twissToFile(resultRequest, getTwissInitialConditions(), tfsFile);
    }

    @Override
    public TfsResult twissToFile(TfsResultRequest resultRequest, TwissInitialConditions customTwissInitialConditions,
            File tfsFile) throws JMadModelException {

        boolean keepOutput = ((JMadKernelImpl) this.getKernel()).isKeepOutputFile();
        ((JMadKernelImpl) this.getKernel()).setKeepOutputFile(true);

        TfsResult tfsResult = this.twiss(resultRequest, customTwissInitialConditions);

        try {
            FileUtil.copyFile(this.getKernel().getOutputFile(), tfsFile);
            this.getKernel().getOutputFile().delete();
            ((JMadKernelImpl) this.getKernel()).setKeepOutputFile(keepOutput);

        } catch (Exception e) {
            throw new JMadModelException("Could not write twiss output to File [" + tfsFile.getAbsolutePath() + "]", e);
        }

        return tfsResult;
    }

    @Override
    public TfsResult twiss(TfsResultRequest resultRequest, TwissInitialConditions customTwissInitialConditions)
            throws JMadModelException {
        ensureInit();

        Result result = null;
        try {
            if (ModelMode.PTC == getMode()) {
                getKernel().execute(new InitPtcTask());
                processModelFiles(getActiveOpticsDefinition().getPostPtcUniverseFiles());
                result = getKernel().execute(new RunPtcTwiss(customTwissInitialConditions, resultRequest));
                getKernel().execute(new PtcEndCommand());
            } else {
                result = getKernel().execute(new RunTwiss(customTwissInitialConditions, resultRequest));
            }
        } catch (JMadException e) {
            throw new JMadModelException("Error executing twiss.", e);
        }

        if ((resultRequest != null) && (result == null)) {
            throw new JMadModelException("ResultRequest was not null, but twiss returned no result!");
        }

        if ((result != null) && (ResultType.TFS_RESULT != result.getResultType())) {
            throw new JMadModelException("Twiss returned wrong type of result!");
        }

        return (TfsResult) result;

    }

    @Override
    public DynapResult dynap(DynapResultRequest dynapResultRequest, TrackInitialCondition trackInitialCondition)
            throws JMadModelException {
        Result result = null;
        try {
            result = this.getKernel().execute(new DynapTask(trackInitialCondition, dynapResultRequest));
        } catch (JMadException e) {
            throw new JMadModelException("Error executing dynap.", e);
        }

        if ((result != null) && ((ResultType.DYNAP_RESULT != result.getResultType()))) {
            throw new JMadModelException("Dynap returned wrong type of result!");
        }

        return (DynapResult) result;
    }

    @Override
    public TrackResult track(TrackResultRequest trackResultRequest, TrackInitialCondition trackInitialCondition)
            throws JMadModelException {
        Result result = null;
        try {
            result = this.getKernel().execute(new TrackTask(trackInitialCondition, trackResultRequest));
        } catch (JMadException e) {
            throw new JMadModelException("Error executing tracking.", e);
        }

        if ((result != null) && ((ResultType.TRACK_RESULT != result.getResultType()))) {
            throw new JMadModelException("Tracking returned wrong type of result!");
        }

        return (TrackResult) result;
    }

    /**
     * @return the actual twiss
     */
    @Override
    public TwissInitialConditions getTwissInitialConditions() {
        return this.twissInitialConditions;
    }

    public void set(Knob strength) throws JMadModelException {
        /** here we have to set always the total value (value+offset) */
        setValue(strength.getName(), strength.getTotalValue());
    }

    @Override
    public void setValue(String name, double value) throws JMadModelException {
        ensureInit();
        dirtyModel = true;
        try {
            getKernel().execute(new SetEqual(name, value));
        } catch (JMadException e) {
            throw new JMadModelException("Unable to set value '" + name + "' with value " + value, e);
        }
        fireBecameDirty();
    }

    @Override
    public void setValues(Map<String, Double> valueNamePairs) throws JMadModelException {
        ensureInit();
        dirtyModel = true;
        try {
            getKernel().execute(new SetListEqual(valueNamePairs));
        } catch (JMadException e) {
            throw new JMadModelException("Unable to set values from give Value-Name Map", e);
        }
        fireBecameDirty();
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            cleanup();
        } catch (JMadModelException e) {
            LOGGER.warn("Error during model - cleanup!", e);
        }
        LOGGER.debug("model was garbage-collected.");
        super.finalize();
    }

    @Override
    public Optic getOptics() throws JMadModelException {
        calcOpticsIfDirty();
        return optics;
    }

    @Override
    public void calcOpticsIfDirty() throws JMadModelException {
        if (dirtyModel) {
            calcOptics();
        }
    }

    @Override
    public KnobManager getKnobManager() {
        return this.knobManager;
    }

    @Override
    public String getName() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(this.getModelDefinition().getName());

        RangeDefinition rangeDefinition = this.getActiveRangeDefinition();
        if (rangeDefinition != null) {
            stringBuilder.append(" - " + rangeDefinition.getSequenceDefinition().getName());
        }

        return stringBuilder.toString();
    }

    @Override
    public void setActiveOpticsDefinition(OpticsDefinition newActiveOpticsDefinition) throws JMadModelException {
        if (newActiveOpticsDefinition == null) {
            return;
        }
        this.knobManager.cleanup();

        if (isInitialized()) {
            processModelFiles(newActiveOpticsDefinition.getInitFiles());
        }
        this.activeOpticsDefinition = newActiveOpticsDefinition;

        /*
         * ensure current active range is active XXX: this is only required as the current ABP Optics are not matched
         * correctly. during matching the lhcb2 sequence is always the last active sequence and inside madx it is not
         * possible to determine which sequence is active. therefore reload the last active range on java level.
         */
        RangeDefinition rangeDefinition = getActiveRangeDefinition();
        if (rangeDefinition != null) {
            this.setActiveRangeDefinition(getActiveRangeDefinition());
        }

        LOGGER.info("Set new active optics: '" + this.activeOpticsDefinition.getName() + "'.");

        this.dirtyModel = true;
        fireOpticsDefinitionChanged();
    }

    @Override
    public OpticsDefinition getActiveOpticsDefinition() {
        return activeOpticsDefinition;
    }

    @Override
    public void setActiveRangeDefinition(RangeDefinition rangeDefinition) throws JMadModelException {

        Range range = new Range(rangeDefinition);

        System.out.println(getActiveOpticsDefinition());
        if (getKernel().isMadxRunning()) {
            Beam beam = rangeDefinition.getSequenceDefinition().getBeam();

            try {
                if (beam == null) {
                    LOGGER.warn("No active beam. This is ok, " + "if beam command was provided in an init-command!?");
                } else {
                    /* set sequence in beam for resbeam command */
                    beam.setSequence(rangeDefinition.getSequenceDefinition().getName());
                    getKernel().execute(new SetBeam(beam));
                }
            } catch (JMadException e) {
                throw new JMadModelException("could not set Beam to '" + beam + "' in model '" + this + "'.", e);
            }

            try {
                if (rangeDefinition.getStartElementName() != null) {
                    getKernel().execute(new CycleSequence(rangeDefinition));
                }

            } catch (JMadException e) {
                throw new JMadModelException("could not cycle the sequence '" + beam + "' in model '" + this + "'.", e);
            }

            try {
                getKernel().execute(new UseCommand(rangeDefinition.getSequenceDefinition().getName(),
                        rangeDefinition.getMadxRange()));
            } catch (JMadException e) {
                throw new JMadModelException("error in excuting use command '" + beam + "' in model '" + this + "'.",
                        e);
            }

            try {
                /* ensure, that the ealigns are not added together */
                getKernel().execute(new EOptionCommand(null, false));

            } catch (JMadException e) {
                throw new JMadModelException(
                        "could not exececute EOption command '" + range.getName() + "' in model '" + this + "'.", e);
            }

            processModelFiles(rangeDefinition.getPostUseFiles());

        }

        /**
         * range is still the same as before --> most likely optic change! We stop here.
         */
        if (this.activeRangeDefinition == rangeDefinition) {
            return;
        }

        this.aperture = null;
        this.activeRangeDefinition = rangeDefinition;
        activeRange = range;

        activeRange.addListener(new RangeListener() {
            @Override
            public void addedMisalignments(MisalignmentConfiguration misalignmentConfiguration) {
                misalignmentConfiguration.addListener(changedMisalignmentConfiguration -> //
                        setMisalignments(singletonList(changedMisalignmentConfiguration)));
                setMisalignments(singletonList(misalignmentConfiguration));
            }

            @Override
            public void addedMisalignments(List<MisalignmentConfiguration> misalignmentConfigurations) {
                setMisalignments(misalignmentConfigurations);
            }
        });

        TwissInitialConditions newTwissInitialConditions;
        newTwissInitialConditions = rangeDefinition.getTwiss().clone();
        setTwissInitialConditions(newTwissInitialConditions);

        if (getKernel().isMadxRunning()) {
            readActiveRange();
        }

        fireRangeChanged(activeRange);
    }

    public void setTwissInitialConditions(TwissInitialConditions twissInitialConditions) {
        this.twissInitialConditions.removeListener(this.twissListener);
        this.twissInitialConditions = twissInitialConditions;
        this.twissInitialConditions.addListener(this.twissListener);
    }

    @Override
    public Range getActiveRange() {
        return activeRange;
    }

    @Override
    public void addListener(JMadModelListener listener) {
        listeners.add(listener);
    }

    @Override
    public void removeListener(JMadModelListener listener) {
        listeners.remove(listener);
    }

    /**
     * notifies all listeners, that the elements were changed
     */
    private void fireElementsChanged() {
        for (JMadModelListener listener : listeners) {
            listener.elementsChanged();
        }
    }

    /**
     * notifies all listeners, that some (or all) optics values have changed.
     */
    private void fireOpticsChanged() {
        for (JMadModelListener listener : listeners) {
            listener.opticsChanged();
        }
    }

    /**
     * notifies all listeners, that a different range is now active
     *
     * @param range the new active range
     */
    private void fireRangeChanged(Range range) {
        for (JMadModelListener listener : listeners) {
            listener.rangeChanged(range);
        }
    }

    @Override
    public void execute(String cmd) {
        FreeText textCmd = new FreeText();
        textCmd.setText(cmd);
        try {
            getKernel().execute(textCmd);
        } catch (MadxTerminatedException e) {
            throw new RuntimeException("Madx Terminated while executing command '" + textCmd + "'.", e);
        } catch (JMadException e) {
            LOGGER.error("error while executing command + '" + cmd + "' in madx.", e);
        }

    }

    @Override
    public void call(File file) {
        try {
            getKernel().execute(new CallCommand(file));
        } catch (MadxTerminatedException e) {
            throw new RuntimeException("Madx Terminated while calling file '" + file.getAbsolutePath() + "'.", e);
        } catch (JMadException e) {
            LOGGER.error("Error while calling file '" + file.getAbsolutePath() + "'.", e);
        }
    }

    /**
     * notify all listeners, that some values changed, so that the actual data is no longer valid.
     */
    protected void fireBecameDirty() {
        for (JMadModelListener listener : listeners) {
            listener.becameDirty();
        }
    }

    private void fireOpticsDefinitionChanged() {
        for (JMadModelListener listener : listeners) {
            listener.opticsDefinitionChanged();
        }
    }

    @Override
    public JMadModelDefinition getModelDefinition() {
        return this.modelDefinition;
    }

    @Override
    public RangeDefinition getActiveRangeDefinition() {
        return this.activeRangeDefinition;
    }

    @Override
    public MatchResult match(MatchResultRequest resultRequest) throws JMadModelException {

        if (resultRequest.getMadxVaryParameters().size() < 1) {
            throw new JMadModelException("At least one Vary Parameter is needed for Matching");
        }

        if (resultRequest.getMatchConstraints().size() < 1) {
            throw new JMadModelException("At least one Constraint is needed for Matching");
        }

        ensureInit();

        if (resultRequest.getSequenceName() == null) {
            resultRequest.setSequenceName(this.activeRangeDefinition.getSequenceDefinition().getName());
        }

        Result result = null;
        try {
            result = getKernel().execute(new RunMatch(resultRequest, getTwissInitialConditions()));
        } catch (JMadException e) {
            throw new JMadModelException("Error executing matching.", e);
        }

        if (result == null) {
            throw new JMadModelException("Matching returned no result!");
        }

        if (ResultType.MATCH_RESULT != result.getResultType()) {
            throw new JMadModelException("Matching returned wrong type of result!");
        }

        MatchResultImpl matchResult = (MatchResultImpl) result;

        // Check if successful
        if (matchResult.getFinalPenalty() <= resultRequest.getMatchMethod().getTolerance()) {
            matchResult.setSuccessful(true);
        } else {
            matchResult.setSuccessful(false);
        }

        // Get global Optics Values from TwissSummary
        TfsSummary twissSummary = this.calcTwissSummary();

        // Prepare TfsRequest for local Constraints
        TfsResultRequestImpl tfsReq = new TfsResultRequestImpl();
        Map<String, Map<String, Double>> localConstraints = new HashMap<>(0);

        for (MatchConstraint mC : resultRequest.getMatchConstraints()) {
            if (mC.isGlobal()) {
                for (String gP : mC.getParameterSettings().keySet()) {
                    MatchConstraintResultGlobal mCrG = new MatchConstraintResultGlobal(gP,
                            twissSummary.getDoubleValue(gP));
                    mCrG.setTargetValue(mC.getParameterSettings().get(gP));
                    matchResult.addConstrainParameterResult(mCrG);
                }
            } else {
                MatchConstraintLocal mCL = (MatchConstraintLocal) mC;

                // Only read out local Constraints if they are for a single
                // Element... Copy Element/ConstraintNames and Values to Map for
                // readOut and update of TargetValue
                if (mCL.getMadxRange().isElement()) {
                    localConstraints.put(mCL.getMadxRange().getMadxString(), mCL.getParameterSettings());
                }
            }
        }

        if (!localConstraints.isEmpty()) {

            for (Entry<String, Map<String, Double>> entry : localConstraints.entrySet()) {

                tfsReq.addElementFilter(entry.getKey());

                for (String var : entry.getValue().keySet()) {
                    MadxTwissVariable newVar = MadxTwissVariable.fromMadxName(var);

                    if (!tfsReq.getResultVariables().contains(newVar)) {
                        tfsReq.addVariable(newVar);
                    }
                }
            }

            // Better add this, otherwise no iteration
            // through names possible, obviously ;)
            tfsReq.addVariable(MadxTwissVariable.NAME);

            TfsResult twissResult = this.twiss(tfsReq);

            for (Entry<String, Map<String, Double>> gP : localConstraints.entrySet()) {

                int elIdx = twissResult.getElementIndex(gP.getKey());

                for (Entry<String, Double> entry : gP.getValue().entrySet()) {
                    MadxTwissVariable newVar = MadxTwissVariable.fromMadxName(entry.getKey());

                    MatchConstraintResultLocal mCrL = new MatchConstraintResultLocal(
                            gP + Element.ATTR_SEPARATOR + entry.getKey(), //
                            twissResult.getDoubleData(newVar).get(elIdx));
                    mCrL.setTargetValue(entry.getValue());

                    matchResult.addConstrainParameterResult(mCrL);
                }
            }
        }

        // update Model
        for (MadxVaryResult rPar : ((MatchResult) result).getVaryParameterResults()) {
            for (MadxVaryParameter vPar : resultRequest.getMadxVaryParameters()) {
                if (vPar.getName().equals(rPar.getName())) {
                    if (vPar.getMadxParameter() instanceof ElementAttribute) {
                        ((ElementAttribute) vPar.getMadxParameter()).setValue(rPar.getFinalValue());
                    } else if (vPar.getMadxParameter() instanceof SimpleStrength) {
                        ((AbstractKnob) vPar.getMadxParameter()).setValue(rPar.getFinalValue());
                    }
                }
            }
        }

        this.dirtyModel = true;

        return (MatchResult) result;
    }

    @Override
    public void saveBeta(String name, String location, boolean runDummyTwiss) throws JMadModelException {

        ensureInit();

        try {
            getKernel().execute(
                    new SaveBetaCommand(name, location, this.activeRangeDefinition.getSequenceDefinition().getName()));
            if (runDummyTwiss) {
                this.execute("twiss;");
            }
        } catch (JMadException e) {
            throw new JMadModelException("Error executing saveBeta.", e);
        }
    }

    @Override
    public TfsSummary calcTwissSummary() throws JMadModelException {
        TfsResult result = twiss(TfsResultRequestImpl.createSummaryOnlyRequest());
        return result.getSummary();
    }

    @Override
    public Aperture getAperture() {
        return this.aperture;
    }

    @Override
    public void loadAperture() {
        if (this.aperture != null) {
            LOGGER.info("Aperture already loaded. Nothing to do.");
            return;
        }
        ApertureDefinition definition = getActiveRangeDefinition().getApertureDefinition();
        if (definition == null) {
            LOGGER.warn("No aperture defined for current range. Nothing to do.");
            return;
        }

        ApertureReader reader = new ApertureReaderImpl();

        ModelFileFinder finder = getModelFileFinder();
        Aperture newAperture = reader.readIndex(finder.getFile(definition.getIndexFile(), getKernel()));

        for (ModelFile modelFile : definition.getPartFiles()) {
            reader.readValues(finder.getFile(modelFile, getKernel()), newAperture);
        }
        this.aperture = newAperture;
    }

    @Override
    public ModelFileFinder getModelFileFinder() {
        if (getModelFileFinderManager() == null) {
            LOGGER.warn("ModelDefinitionManager not configured!");
            return null;

        }
        return getModelFileFinderManager().getModelFileFinder(getModelDefinition());
    }

    @Override
    public StrengthVarSet getStrengthsAndVars() {
        return this.strengthVarManager.getStrengthVarSet();
    }

    public void setKernel(JMadKernel kernel) {
        this.kernel = kernel;
    }

    @Override
    public JMadModelStartupConfiguration getStartupConfiguration() {
        return startupConfiguration;
    }

    public void setModelFileFinderManager(ModelFileFinderManager modelFileFinderManager) {
        this.modelFileFinderManager = modelFileFinderManager;
    }

    public ModelFileFinderManager getModelFileFinderManager() {
        return modelFileFinderManager;
    }

    @Override
    public void setStartupConfiguration(JMadModelStartupConfiguration startupConfiguration) {
        this.startupConfiguration = startupConfiguration;
    }

    @Override
    public String toString() {
        return getName();
    }

    @Override
    public String getDescription() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("Model [" + this.getModelDefinition().getName() + "]");

        RangeDefinition rangeDefinition = this.getActiveRangeDefinition();
        if (rangeDefinition != null) {
            stringBuilder.append(" - Sequence [" + rangeDefinition.getSequenceDefinition().getName() + "]");
            stringBuilder.append(" - Range [" + rangeDefinition.getName() + "]");
        }

        OpticsDefinition opticDefinition = this.getActiveOpticsDefinition();
        if (opticDefinition != null) {
            stringBuilder.append(" - Optic [" + opticDefinition.getName() + "]");
        }

        return stringBuilder.toString();
    }

    @Override
    public ModelMode getMode() {
        return this.modelMode;
    }

    @Override
    public void setMode(ModelMode modelMode) {
        this.modelMode = modelMode;
    }

    @Override
    public void setTitle(String title) {
        this.execute("title,\"" + title + "\";");
    }

    @Override
    public List<MisalignmentConfiguration> getMisalignments() {
        // TODO
        List<MisalignmentConfiguration> toReturn = new ArrayList<>();
        try {
            TfsResult misalignmentsRaw = getMisalignmentsRaw();
            toReturn = convertToMisalignments(misalignmentsRaw);
        } catch (JMadModelException e) {
            LOGGER.warn("Cannot complete the misalignment retrival task due: ", e);
        }
        return toReturn;
    }

    private TfsResult getMisalignmentsRaw() throws JMadModelException {
        ensureInit();

        TfsResult valuesResult = null;
        try {
            // XXX first approach ONLY QUADRUPOLES information is extracted!
            Result result = getKernel().execute(new GetMisalignmentsTask("MQ.*"));
            if (ResultType.TFS_RESULT != result.getResultType()) {
                throw new JMadModelException("GetValues returned wrong type of result!");
            }
            valuesResult = (TfsResult) result;
        } catch (JMadException e) {
            LOGGER.warn("Cannot complete the misalignment retrival task due: ", e);
        }
        return valuesResult;
    }

    private List<MisalignmentConfiguration> convertToMisalignments(TfsResult misalignmentsRaw) {
        List<MisalignmentConfiguration> toReturn = new ArrayList<>();
        for (String oneElement : misalignmentsRaw.getStringData(MadxGlobalVariable.NAME)) {
            Integer elementIndex = misalignmentsRaw.getElementIndex(oneElement);
            MisalignmentConfiguration misalignmentConfiguration = new MisalignmentConfiguration(oneElement);
            misalignmentConfiguration.getMisalignment()
                    .setDeltaY(getVariableValue(misalignmentsRaw, elementIndex, EalignVariables.DX));
            misalignmentConfiguration.getMisalignment()
                    .setDeltaX(getVariableValue(misalignmentsRaw, elementIndex, EalignVariables.DY));
            misalignmentConfiguration.getMisalignment()
                    .setDeltaS(getVariableValue(misalignmentsRaw, elementIndex, EalignVariables.DS));
            misalignmentConfiguration.getMisalignment()
                    .setDeltaPhi(getVariableValue(misalignmentsRaw, elementIndex, EalignVariables.DPHI));
            misalignmentConfiguration.getMisalignment()
                    .setDeltaPsi(getVariableValue(misalignmentsRaw, elementIndex, EalignVariables.DPSI));
            misalignmentConfiguration.getMisalignment()
                    .setDeltaTheta(getVariableValue(misalignmentsRaw, elementIndex, EalignVariables.DTHETA));
            misalignmentConfiguration.getMisalignment()
                    .setMonitorReadErrorX(getVariableValue(misalignmentsRaw, elementIndex, EalignVariables.MREX));
            misalignmentConfiguration.getMisalignment()
                    .setMonitorReadErrorY(getVariableValue(misalignmentsRaw, elementIndex, EalignVariables.MREY));
            misalignmentConfiguration.getMisalignment()
                    .setApertureErrorX(getVariableValue(misalignmentsRaw, elementIndex, EalignVariables.AREX));
            misalignmentConfiguration.getMisalignment()
                    .setApertureErrorY(getVariableValue(misalignmentsRaw, elementIndex, EalignVariables.AREY));
            toReturn.add(misalignmentConfiguration);
        }

        return toReturn;
    }

    /**
     * @param misalignmentsRaw
     * @param elementIndex
     * @param variable
     * @return
     */
    protected Double getVariableValue(TfsResult misalignmentsRaw, Integer elementIndex, EalignVariables variable) {
        return misalignmentsRaw.getDoubleData(variable).get(elementIndex);
    }

    public void setStrengthVarManager(StrengthVarManager strengthVarManager) {
        this.strengthVarManager = strengthVarManager;
    }

    @Override
    public StrengthVarManager getStrengthVarManager() {
        return this.strengthVarManager;
    }
}