content: /**
   * Copyright 2020 Alexander Herzog
   *
   * 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.
   */
  package simulator.runmodel;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.TreeMap;
  import java.util.concurrent.ExecutionException;
  import java.util.concurrent.Future;
  import java.util.concurrent.LinkedBlockingQueue;
  import java.util.concurrent.ThreadFactory;
  import java.util.concurrent.ThreadPoolExecutor;
  import java.util.concurrent.TimeUnit;
  
  import org.apache.commons.math3.util.FastMath;
  
  import language.Language;
  import mathtools.NumberTools;
  import mathtools.distribution.tools.RandomGeneratorMode;
  import scripting.java.ImportSettingsBuilder;
  import simulator.StartAnySimulator;
  import simulator.StartAnySimulator.AdditionalPrepareErrorInfo;
  import simulator.builder.RunModelCreator;
  import simulator.coreelements.RunElement;
  import simulator.editmodel.EditModel;
  import simulator.elements.RunElementSourceTable;
  import simulator.elements.RunSource;
  import simulator.simparser.ExpressionCalc;
  import simulator.simparser.ExpressionCalcModelUserFunctions;
  import simulator.simparser.ExpressionMultiEval;
  import simulator.simparser.symbols.CalcSymbolClientUserData;
  import simulator.statistics.Statistics;
  import simulator.statistics.Statistics.CorrelationMode;
  import tools.SetupData;
  import ui.modeleditor.ModelSequence;
  import ui.modeleditor.ModelSequenceStep;
  import ui.modeleditor.coreelements.ModelElement;
  import ui.modeleditor.coreelements.ModelElementBox;
  import ui.modeleditor.coreelements.ModelElementPosition;
  import ui.modeleditor.elements.ElementWithNewVariableNames;
  import ui.modeleditor.elements.ElementWithScript;
  import ui.modeleditor.elements.InteractiveElement;
  import ui.modeleditor.elements.ModelElementAnimationConnect;
  import ui.modeleditor.elements.ModelElementDispose;
  import ui.modeleditor.elements.ModelElementDisposeWithTable;
  import ui.modeleditor.elements.ModelElementSource;
  import ui.modeleditor.elements.ModelElementSourceDB;
  import ui.modeleditor.elements.ModelElementSourceDDE;
  import ui.modeleditor.elements.ModelElementSourceMulti;
  import ui.modeleditor.elements.ModelElementSourceRecord;
  import ui.modeleditor.elements.ModelElementSourceTable;
  import ui.modeleditor.elements.ModelElementSub;
  
  /**
   * Laufzeit-Modell
   * Dieses Modell wird read-only in der Simulation verwendet. Im Gegensatz zu dem Editor-Modell ist es auf Konsistenz geprft,
   * Bediensationen sind per Referenzen verknpft, nicht mehr nur durch Freitextfelder.
   * @author Alexander Herzog
   * @see EditModel
   * @see RunModel#getRunModel(EditModel, String, boolean, boolean)
   */
  public class RunModel {
  	/**
  	 * Anzahl der zu simulierenden Kundenanknfte (kann <code>-1</code> sein, wenn das Simulationsende nicht ber die Zhlung der Kundenanknfte erfolgen soll)
  	 */
  	public long clientCount;
  
  	/**
  	 * Anzahl an zu simulierenden Kundenanknfte (nur fr die Bestimmung der Warm-Up-Zeit)
  	 */
  	public long clientCountModel;
  
  	/**
  	 * Gibt an, wie viele Kunden insgesamt eintreffen werden.<br>
  	 * Bei Modellen, die nur Tabellenquellen verwenden, ist dies das Minimum aus der Summe aller Tabellenzeilen und {@link RunModel#clientCount}. Bei allen anderen Modellen ist dies stets {@link RunModel#clientCount}.
  	 */
  	public long realArrivingClientCount;
  
  	/**
  	 * Lnge der Einschwingphase (als Anteil der Kundenanknfte), bevor die Statistikzhlung beginnt.<br>
  	 * Die Einschwingphase wird nicht von der Kundenanzahl abgezogen, sondern besteht aus zustzlichen Anknften.<br>
  	 * (Soll keine Kundenanzahl-abhngige Einschwingphase verwendet werden, so steht dieser Wert auf 0.)
  	 */
  	public double warmUpTime;
  
  	/**
  	 * Zeitpunkt (in Sekunden), zu dem die Einschwingphase beendet werden soll.<br>
  	 * (Werte &le;0 bedeuten, dass keine zeitgesteuerte Beendigung der Einschwingphase erfolgen soll.)
  	 */
  	public long warmUpTimeTime;
  
  	/**
  	 * Gibt an, wie oft der Simulationslauf als Ganzes wiederholt werden soll.
  	 */
  	public int repeatCount;
  
  	/**
  	 * Sollen wiederholte Simulationslufe ggf. aufgeteilt werden, um alle CPU-Kerne auszulasten?
  	 */
  	public boolean repeatAllowSplit;
  
  	/**
  	 * Gibt an, durch wie viel die Anzahl an Kunden fr einen Thread geteilt werden soll.<br>
  	 * (Dieser Wert steht auch in den SimulationData zur Verfgung.)
  	 */
  	public int clientCountDiv;
  
  	/**
  	 * Abbruchbedingung (kann <code>null</code> sein, wenn keine Abbruchbedingung definiert ist)
  	 */
  	public ExpressionMultiEval terminationCondition;
  
  	/**
  	 * Abbruchzeitpunkt in Sekunden (kann -1 sein, wenn keine Abbruchzeit definiert ist)
  	 */
  	public long terminationTime;
  
  	/**
  	 * Abbruch bei Erreichen des Batch-Means-Konfidenzradius fr die Wartezeiten (-1, wenn nicht zu verwenden)
  	 */
  	public double terminationWaitingTimeConfidenceHalfWidth;
  
  	/**
  	 * Abbruch bei Erreichen des Batch-Means-Konfidenzradius fr die Wartezeiten fr das angegebene Niveau (-1, wenn nicht zu verwenden)
  	 */
  	public double terminationWaitingTimeConfidenceLevel;
  
  	/**
  	 * Liste mit Namen aller vorhandenen Kundentypen
  	 */
  	public String[] clientTypes;
  
  	/**
  	 * Zuordnung von Kundentypnamen zu Indices in {@link #clientTypes}
  	 * @see #clientTypes
  	 */
  	public Map<String,Integer> clientTypesMap;
  
  	/**
  	 * Kosten fr die Zeitanteile pro Sekunde Kundentyp
  	 */
  	public double[][] clientCosts;
  
  	/**
  	 * Liste mit den Icons aller vorhandenen Kundentypen
  	 */
  	public String[] clientTypeIcons;
  
  	/**
  	 * Liste mit allen Variablennamen (in der Reihenfolge wie sie als Parameter dem Calc-System bergeben werden)
  	 */
  	public String[] variableNames;
  
  	/**
  	 * Initiale Werte fr die Variablen (knnen teilweise <code>null</code> sein, wenn nicht explizit gesetzt werden soll)
  	 */
  	public ExpressionCalc[] variableInitialValues;
  
  	/**
  	 * Zustzliche Variablennamen, die immer belegt sein sollen.
  	 */
  	public static final String[] additionalVariables=new String[]{"w","t","p"};
  
  	/**
  	 * Variablenwerte in der Statistik erfassen?
  	 * @see RunData#updateVariableValueForStatistics(SimulationData, int)
  	 */
  	public boolean recordVariableValuesToStatistic;
  
  	/**
  	 * Werte der globalen Zuordnung in der Statistik erfassen?
  	 * @see RunData#updateMapValuesForStatistics(SimulationData)
  	 */
  	public boolean recordMapValueToStatistic;
  
  	/**
  	 * Startwerte fr die globale Zuordnung
  	 */
  	public Map<String,Object> globalMapInitial;
  
  	/**
  	 * Liste der Modell-Elemente des Laufzeitmodells
  	 * @see RunModel#elementsFast
  	 */
  	public Map<Integer,RunElement> elements;
  
  	/**
  	 * Direktzugriff-Liste der Modell-Elemente des Laufzeitmodells
  	 * @see RunModel#elements
  	 */
  	public RunElement[] elementsFast;
  
  	/**
  	 * Zuordnung von Modell-Element-Namen zu IDs
  	 */
  	public TreeMap<String,Integer> namesToIDs;
  
  	/**
  	 * Globales Ressourcen-Objekt, welches nur bei der Initialisierung der thread-lokalen Laufzeitdaten als
  	 * Kopier-Basis verwendet wird.
  	 */
  	public RunDataResources resourcesTemplate;
  
  	/**
  	 * Globales Transporter-Objekt, welches nur bei der Initialisierung der thread-lokalen Laufzeitdaten als
  	 * Kopier-Basis verwendet wird.
  	 */
  	public RunDataTransporters transportersTemplate;
  
  	/**
  	 * Zufallszahlen-Generator-Modus
  	 */
  	public RandomGeneratorMode randomGeneratorMode;
  
  	/**
  	 * Festen Seed fr den Zufallszahlengenerator verwenden?
  	 * @see #fixedSeed
  	 */
  	public boolean useFixedSeed;
  
  	/**
  	 * Seed fr den Zufallszahlengenerator.<br>
  	 * Ist nur aktiv, wenn <code>useFixedSeed=true</code> ist.
  	 * @see #useFixedSeed
  	 */
  	public long fixedSeed;
  
  	/**
  	 * Maximaler Autokorrelationswert der bei der Erfassung der Daten vorgesehen werden soll.
  	 * @see RunModel#correlationMode
  	 */
  	public int correlationRange;
  
  	/**
  	 * Art der Erfassung der Autokorrelation
  	 * @see CorrelationMode#CORRELATION_MODE_OFF
  	 * @see CorrelationMode#CORRELATION_MODE_FAST
  	 * @see CorrelationMode#CORRELATION_MODE_FULL
  	 */
  	public Statistics.CorrelationMode correlationMode;
  
  	/**
  	 * Gibt an ob (bei &gt;1) und wenn ja von welcher Gre die Batches
  	 * sein sollen, auf deren Basis Batch-Means berechnet werden sollen.
  	 */
  	public int batchMeansSize;
  
  	/**
  	 * Sollen die individuellen Wartezeiten gespeichert werden?
  	 * @see Statistics#clientsAllWaitingTimesCollector
  	 */
  	public boolean collectWaitingTimes;
  
  	/**
  	 * Namen der Fertigungsplne
  	 */
  	public String[] sequenceNames;
  
  	/**
  	 * Liste der Namen der Zielstationen in den einzelnen Schritten der einzelnen Fertigungsplne
  	 */
  	public String[][] sequenceStepStationNames;
  
  	/**
  	 * Liste der IDs der Zielstationen in den einzelnen Schritten der einzelnen Fertigungsplne
  	 */
  	public int[][] sequenceStepStationIDs;
  
  	/**
  	 * 0-basierende Nummern der nchsten Schritte in den einzelnen Schritten der einzelnen Fertigungsplne.<br>
  	 * -1 bedeutet dabei, dass kein weiterer Schritt folgt.
  	 */
  	public int[][] sequenceStepNext;
  
  	/**
  	 * ClientData-Nummern fr die Zuweisungen pro Schritt
  	 */
  	public int[][][] sequenceStepAssignmentNr;
  
  	/**
  	 * Ausdrcke fr die Zuweisungen pro Schritt
  	 */
  	public String[][][] sequenceStepAssignmentExpression;
  
  	/**
  	 * Welcher Sekundenwert soll in der Verteilungsstatistik maximal erfasst werden (Angabe in Stunden)?
  	 */
  	public int distributionRecordHours;
  
  	/**
  	 * Welcher Zustand (z.B. Anzahl an Kunden im System) soll maximal erfasst werden?
  	 */
  	public int stateRecordSize;
  
  	/**
  	 * Wie viele verschiedene Kundendaten-Werte sollen in der Hufigkeitsverteilung maximal erfasst werden?
  	 */
  	public int distributionRecordClientDataValues;
  
  	/**
  	 * Soll der Welford-Algorithmus zur Erfassung der Varianz verwendet werden? (langsamer, aber bei ganz kleinen Variationskoeffizienten exakter)
  	 */
  	public boolean useWelford;
  
  	/**
  	 * Simulation abbrechen, wenn ein Rechenausdruck nicht ausgerechnet werden kann.
  	 */
  	public boolean stoppOnCalcError;
  
  	/**
  	 * Gibt an, ob es sich um eine Animation (<code>true</code>) oder um eine reine Simulation ohne Grafikausgabe (<code>false</code>) handelt
  	 */
  	public boolean isAnimation;
  
  	/**
  	 * Element zur Verbindung von Simulationsmodell und Animationssystem (ist nur mit einem Wert ungleich <code>null</code> belegt, wenn <code>isAnimation</code> auf <code>true</code> gesetzt ist)
  	 */
  	public ModelElementAnimationConnect animationConnect;
  
  	/**
  	 * Zeitabstand in dem fr Bedingung- und hnliche Stationen zustzliche zeitabhngige Checks durchgefhrt werden sollen.
  	 * Werte &le;0 bedeuten, dass keine Checks stattfinden. Sonst ist der Wert die Simulationszeitschrittanzahl zwischen zwei Checks.
  	 */
  	public long timedChecksDelta=-1;
  
  	/**
  	 * Zhlung wie hufig welche Stationsbergnge stattgefunden haben
  	 */
  	public boolean recordStationTransitions;
  
  	/**
  	 * Erfassung aller Kundenpfade
  	 */
  	public boolean recordClientPaths;
  
  	/**
  	 * Erfassung der jeweils gesamten Zeiten eines Kunden an einer Station (d.h. mehrere Bedienungen desselben Kunden an einer Station als Summe)?
  	 */
  	public boolean recordStationTotalClientTimes;
  
  	/**
  	 * Erfassung der Text-Kundendatenfelder in der Statistik
  	 */
  	public boolean recordClientTextData;
  
  	/**
  	 * Simulation bei einem Scripting-Fehler abbrechen
  	 */
  	public boolean cancelSimulationOnScriptError;
  
  	/**
  	 * Sollen auch Kunden, die das System am Ende noch nicht verlassen haben, in der Statistik erfasst werden knnen (<code>true</code>). Dies verlangsamt die Simulation.
  	 */
  	public boolean recordIncompleteClients;
  
  	/**
  	 * Verzeichnis fr optionale externe Java-Klassendateien
  	 * (wird direkt aus {@link EditModel} bernommen und
  	 * spter von {@link SimulationData} ausgewertet.
  	 */
  	public String pluginsFolder;
  
  	/**
  	 * Einstellungen zu Import und Classpath fr Skripte
  	 */
  	public ImportSettingsBuilder javaImports;
  
  	/**
  	 * Pfad zur zugehrigen Modelldatei
  	 * (als Basis fr relative Pfade in Ausgabeelementen)
  	 */
  	public String modelPath;
  
  	/**
  	 * Modellspezifische nutzerdefinierte Funktionen
  	 */
  	public ExpressionCalcModelUserFunctions modelUserFunctions;
  
  	/**
  	 * Multiplikativer Faktor zur Umrechnung von Sekunden in Simulationszeitschritte
  	 * @see #scaleToSeconds
  	 */
  	public long scaleToSimTime;
  
  	/**
  	 * Multiplikativer Faktor zur Umrechnung von Simulationszeitschritten in Sekunden
  	 * @see #scaleToSimTime
  	 */
  	public double scaleToSeconds;
  
  	/**
  	 * Ein <code>RunModel</code> kann nicht direkt erzeugt werden, sondern es kann nur ein <code>EditModel</code>
  	 * mittels der Funktion <code>getRunModel</code> in ein <code>RunModel</code> umgeformt werden. Dabei wird das
  	 * Modell auf Konsistenz geprft und alle notwendigen Verknpfungen werden hergestellt.
  	 * @see EditModel
  	 * @see RunModel#getRunModel(EditModel, String, boolean, boolean)
  	 */
  	private RunModel() {
  		elements=new HashMap<>();
  		namesToIDs=new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
  	}
  
  	/**
  	 * Liefert den Index den angegebenen Clienttypennamens in dem <code>clientTypes</code>-Array (ohne Bercksichtigung von Gro- und Kleinschreibung)
  	 * @param clientTypeName	Name, der dem Array gefunden werden soll
  	 * @return	Index des Namens in dem <code>clientTypes</code>-Array oder -1, wenn der Name nicht in dem Array vorkommt
  	 * @see #clientTypes
  	 */
  	public int getClientTypeNr(final String clientTypeName) {
  		if (clientTypeName==null) return -1;
  		for (int i=0;i<clientTypes.length;i++) if (clientTypeName.equalsIgnoreCase(clientTypes[i])) return i;
  		return -1;
  	}
  
  	/**
  	 * Liefert eine Liste mit allen globalen Variablen in dem Modell.
  	 * @param editModel	Modell aus dem die Daten ausgelesen werden sollen
  	 * @return	Liefert im Erfolgsfall ein Objekt vom Typ <code>List&lt;String&gt;</code>, sonst einen String
  	 */
  	public static Object getAllVariableNames(final EditModel editModel) {
  		final List<String> variables=new ArrayList<>();
  		for (String variable: editModel.globalVariables.stream().map(variable->variable.getName()).toArray(String[]::new)) {
  			boolean inList=false;
  			for (String s: variables) if (s.equalsIgnoreCase(variable)) {inList=true; break;}
  			for (String s: RunModel.additionalVariables) if (s.equalsIgnoreCase(variable)) {inList=true; break;}
  			if (!inList) variables.add(variable);
  		}
  		for (ModelElement element : editModel.surface.getElements()) if (element instanceof ElementWithNewVariableNames) {
  			for (String variable: ((ElementWithNewVariableNames)element).getVariables()) {
  				if (CalcSymbolClientUserData.testClientData(variable)>=0) continue;
  				if (CalcSymbolClientUserData.testClientDataString(variable)!=null) continue;
  				boolean varNameOk=ExpressionCalc.checkVariableName(variable);
  				if (!varNameOk) return String.format(Language.tr("Simulation.Creator.InvalidVariableName"),element.getId(),variable);
  				boolean inList=false;
  				for (String s: variables) if (s.equalsIgnoreCase(variable)) {inList=true; break;}
  				for (String s: RunModel.additionalVariables) if (s.equalsIgnoreCase(variable)) {inList=true; break;}
  				if (!inList) variables.add(variable);
  			}
  		}
  		variables.addAll(Arrays.asList(RunModel.additionalVariables));
  		return variables;
  	}
  
  	/**
  	 * Stellt die Liste der globalen Variablen zusammen und belegt diese mit ihren Startwerten.
  	 * @param editModel	Editor-Modell dem die Daten entnommen werden soll
  	 * @param runModel	Laufzeit-Modell in das die entsprechenden Daten eingetragen werden sollen
  	 * @return	Liefert im Erfolgsfall <code>null</code>, sonst eine Fehlermeldung
  	 * @see #getRunModel(EditModel, String, boolean, boolean)
  	 */
  	private static String initVariables(final EditModel editModel, final RunModel runModel) {
  		/* Variablenliste aufstellen */
  		final Object varList=getAllVariableNames(editModel);
  		if (varList instanceof String) return (String)varList;
  		@SuppressWarnings("unchecked")
  		final List<String> variables=(List<String>)varList;
  		runModel.variableNames=variables.toArray(String[]::new);
  
  		/* Initiale Werte fr Variablen bestimmen */
  		runModel.variableInitialValues=new ExpressionCalc[runModel.variableNames.length];
  		for (int i=0;i<editModel.globalVariables.size();i++) {
  			final var globalVariable=editModel.globalVariables.get(i);
  			final String varName=globalVariable.getName();
  			final String varExpression=globalVariable.getExpression();
  			final ExpressionCalc calc=new ExpressionCalc(runModel.variableNames,runModel.modelUserFunctions);
  			final int error=calc.parse(varExpression);
  			if (error>=0) return String.format(Language.tr("Simulation.Creator.InvalidInitialVariableExpression"),varName,varExpression,error+1);
  			int index=-1;
  			for (int j=0;j<runModel.variableNames.length;j++) if (runModel.variableNames[j].equals(varName)) {index=j; break;}
  			if (index>=0) runModel.variableInitialValues[index]=calc;
  		}
  
  		/* Konfiguration zur Erfassung der Variablenwerte in der Statistik */
  		runModel.recordVariableValuesToStatistic=(editModel.variableRecord==EditModel.VariableRecord.VARIABLES) || (editModel.variableRecord==EditModel.VariableRecord.MAPS_VARIABLES);
  		runModel.recordMapValueToStatistic=(editModel.variableRecord==EditModel.VariableRecord.MAPS_VARIABLES);
  
  		/* Startwerte fr die globale Zuordnung */
  		runModel.globalMapInitial=editModel.globalMapInitial;
  
  		return null;
  	}
  
  	/**
  	 * bertrgt die allgemeinen Daten (Anzahl an Wiederholungen, Einschwingphase usw.) vom Editor- in das Laufzeit-Modell.<br>
  	 * Es werden zunchst nur die Daten bertragen, die keine Stationen im Laufzeit-Modell voraussetzen. Die weiteren globalen
  	 * Daten werden nach der Erstellung der Stationen von {@link #initGeneralData2(EditModel, RunModel)} bertragen.
  	 * @param editModel	Editor-Modell dem die Daten entnommen werden soll
  	 * @param modelPath	Pfad zur zugehrigen Modelldatei (als Basis fr relative Pfade in Ausgabeelementen)
  	 * @param runModel	Laufzeit-Modell in das die entsprechenden Daten eingetragen werden sollen
  	 * @return	Liefert im Erfolgsfall <code>null</code>, sonst eine Fehlermeldung
  	 * @see #getRunModel(EditModel, String, boolean, boolean)
  	 */
  	private static String initGeneralData(final EditModel editModel, final String modelPath, final RunModel runModel) {
  		/* Auch Modelle ohne explizites Ende-Kriterium zulassen. In ModelPropertiesDialogPageSimulation erfolgt hierzu eine Warnung. */
  		/* if (!editModel.useClientCount && !editModel.useFinishTime && !(editModel.useTerminationCondition && !editModel.terminationCondition.isBlank()) && !editModel.useFinishConfidence) return Language.tr("Simulation.Creator.NoEndCriteria"); */
  
  		/* Simulationszeiteinheit */
  		runModel.scaleToSimTime=editModel.timeStepsPerSecond;
  		runModel.scaleToSeconds=1.0/runModel.scaleToSimTime;
  
  		/* Anzahl der zu simulierenden Kundenanknfte */
  		if (editModel.useClientCount) {
  			if (editModel.clientCount<=0) return String.format(Language.tr("Simulation.Creator.InvalidNumberOfClients"),editModel.clientCount);
  			runModel.clientCount=editModel.clientCount;
  		} else {
  			runModel.clientCount=-1;
  		}
  
  		/* Einschwingphase */
  		runModel.clientCountModel=Math.max(0,editModel.clientCount);
  		if (editModel.warmUpTime<0) return String.format(Language.tr("Simulation.Creator.InvalidWarmUpPeriod"),NumberTools.formatNumber(editModel.warmUpTime));
  		runModel.warmUpTime=editModel.warmUpTime;
  
  		runModel.warmUpTimeTime=(editModel.warmUpTimeTime>0)?editModel.warmUpTimeTime:-1;
  
  		/* Wiederholungen der Simulation */
  		if (editModel.repeatCount<1) return String.format(Language.tr("Simulation.Creator.InvalidRepeatCount"),editModel.repeatCount);
  		if (editModel.repeatCount>1) {
  			final String noRepeat=editModel.getNoRepeatReason();
  			if (noRepeat!=null) return String.format(Language.tr("Simulation.Creator.NoRepeatCountAllowed"),editModel.repeatCount,noRepeat);
  		}
  		runModel.repeatCount=editModel.repeatCount;
  		runModel.repeatAllowSplit=SetupData.getSetup().useMultiCoreSimulationOnRepeatedSimulations;
  
  		/* Simulation bei Rechenfehlern abbrechen */
  		runModel.stoppOnCalcError=editModel.stoppOnCalcError;
  
  		/* Liste der Kundentypen */
  		runModel.clientTypes=editModel.surface.getClientTypes().toArray(String[]::new);
  		runModel.clientTypesMap=new HashMap<>();
  		for (int i=0;i<runModel.clientTypes.length;i++) runModel.clientTypesMap.put(runModel.clientTypes[i],i);
  
  		/* Liste mit den Kosten pro Kundentyp */
  		runModel.clientCosts=new double[runModel.clientTypes.length][];
  		for (int i=0;i<runModel.clientCosts.length;i++) runModel.clientCosts[i]=editModel.clientData.getCosts(runModel.clientTypes[i]);
  
  		/* List der Kundentypicons */
  		runModel.clientTypeIcons=new String[runModel.clientTypes.length];
  		for (int i=0;i<runModel.clientTypeIcons.length;i++) runModel.clientTypeIcons[i]=editModel.clientData.getIcon(runModel.clientTypes[i]);
  
  		/* Ressourcen */
  		runModel.resourcesTemplate=new RunDataResources();
  		String error=runModel.resourcesTemplate.loadFromEditResources(editModel.resources,editModel.schedules,runModel.variableNames,runModel);
  		if (error!=null) return error;
  
  		/* Transporter */
  		runModel.transportersTemplate=new RunDataTransporters();
  		error=runModel.transportersTemplate.loadFromEditTransporters(editModel.transporters,editModel.surface,runModel.variableNames,runModel);
  		if (error!=null) return error;
  
  		/* Sequenzen */
  		final int seqCount=editModel.sequences.getSequences().size();
  		runModel.sequenceNames=new String[seqCount];
  		runModel.sequenceStepStationNames=new String[seqCount][];
  		runModel.sequenceStepStationIDs=new int[seqCount][];
  		runModel.sequenceStepNext=new int[seqCount][];
  		runModel.sequenceStepAssignmentNr=new int[seqCount][][];
  		runModel.sequenceStepAssignmentExpression=new String[seqCount][][];
  		for (int i=0;i<seqCount;i++) {
  			final ModelSequence sequence=editModel.sequences.getSequences().get(i);
  			final int stepCount=sequence.getSteps().size();
  			if (sequence.getName()==null || sequence.getName().isEmpty()) return String.format(Language.tr("Simulation.Creator.SequenceNoName"),i+1);
  			if (stepCount==0) return String.format(Language.tr("Simulation.Creator.SequenceNoSteps"),sequence.getName());
  			runModel.sequenceNames[i]=sequence.getName();
  			runModel.sequenceStepStationNames[i]=new String[stepCount];
  			runModel.sequenceStepStationIDs[i]=new int[stepCount];
  			runModel.sequenceStepNext[i]=new int[stepCount];
  			runModel.sequenceStepAssignmentNr[i]=new int[stepCount][];
  			runModel.sequenceStepAssignmentExpression[i]=new String[stepCount][];
  			for (int j=0;j<stepCount;j++) {
  				final ModelSequenceStep step=sequence.getSteps().get(j);
  				if (step.getTarget()==null || step.getTarget().isEmpty()) return String.format(Language.tr("Simulation.Creator.SequenceStepNoTarget"),sequence.getName(),j+1);
  				runModel.sequenceStepStationNames[i][j]=step.getTarget(); /* Zuordnung der IDs erfolgt erst in Schritt 2 in initGeneralData2 */
  				int next=step.getNext();
  				if (next<0) {
  					next=(j==stepCount-1)?-1:(j+1);
  				} else {
  					if (next>=stepCount) return String.format(Language.tr("Simulation.Creator.SequenceInvalidNextStep"),sequence.getName(),j+1,next+1);
  				}
  				runModel.sequenceStepNext[i][j]=next;
  				final int assignCount=step.getAssignments().size();
  				runModel.sequenceStepAssignmentNr[i][j]=new int[assignCount];
  				runModel.sequenceStepAssignmentExpression[i][j]=new String[assignCount];
  				int k=0;
  				for (Map.Entry<Integer,String> entry: step.getAssignments().entrySet()) {
  					runModel.sequenceStepAssignmentNr[i][j][k]=entry.getKey();
  					runModel.sequenceStepAssignmentExpression[i][j][k]=entry.getValue();
  					final int exprError=ExpressionCalc.check(entry.getValue(),editModel.surface.getMainSurfaceVariableNames(editModel.getModelVariableNames(),false),editModel.userFunctions);
  					if (exprError>=0) return String.format(Language.tr("Simulation.Creator.SequenceInvalidExpression"),sequence.getName(),j+1,runModel.sequenceStepAssignmentNr[i][j][k],entry.getValue(),exprError+1);
  					k++;
  				}
  			}
  		}
  
  		/* Zeitabstand in dem fr Bedingung- und hnliche Stationen zustzliche zeitabhngige Checks durchgefhrt werden sollen. */
  		runModel.timedChecksDelta=editModel.timedChecksDelta*editModel.timeStepsPerSecond/1000; /* Editorwert in MS, Laufzeitwert in Simulatorzeitschritten */
  
  		/* Aufzeichnung der Kundenbewegungen */
  		runModel.recordStationTransitions=editModel.recordStationTransitions;
  		runModel.recordClientPaths=editModel.recordClientPaths;
  		runModel.recordStationTotalClientTimes=editModel.recordStationTotalClientTimes;
  
  		/* Aufzeichnung der Text-Kundendatenfelder */
  		runModel.recordClientTextData=editModel.recordClientTextData;
  
  		/* Scripting */
  		runModel.cancelSimulationOnScriptError=SetupData.getSetup().cancelSimulationOnScriptError;
  
  		/* Sollen auch Kunden, die das System am Ende noch nicht verlassen haben, in der Statistik erfasst werden? */
  		runModel.recordIncompleteClients=editModel.recordIncompleteClients;
  
  		/* Plugins-Verzeichnis */
  		runModel.pluginsFolder=editModel.pluginsFolder;
  
  		/* Optionale nutzerdefinierte Imports */
  		runModel.javaImports=new ImportSettingsBuilder(editModel);
  
  		/* Pfad zur zugehrigen Modelldatei (als Basis fr relative Pfade in Ausgabeelementen) */
  		runModel.modelPath=modelPath;
  
  		/* Modellspezifische nutzerdefinierte Funktionen */
  		runModel.modelUserFunctions=new ExpressionCalcModelUserFunctions(editModel.userFunctions);
  
  		return null;
  	}
  
  	/**
  	 * Liefert die Anzahl, wie viele Kunden insgesamt eintreffen werden.<br>
  	 * Bei Modellen, die nur Tabellenquellen verwenden, ist dies das Minimum aus der Summe aller Tabellenzeilen und {@link RunModel#clientCount}. Bei allen anderen Modellen ist dies stets {@link RunModel#clientCount}.
  	 * @param runModel	Laufzeitmodell
  	 * @return	Anzahl an insgesamt eintreffenden Kunden
  	 * @see #realArrivingClientCount
  	 */
  	private static long getArrivingRealClientCount(final RunModel runModel) {
  		long sum=0;
  		for (RunElement element: runModel.elementsFast) {
  			if (element instanceof RunSource) {
  				if (!(element instanceof RunElementSourceTable)) return runModel.clientCount;
  				final long value=((RunElementSourceTable)element).getArrivalCount();
  				if (value<0) return runModel.clientCount; /* Quelle hat die eigenen Daten noch nicht geladen. */
  				sum+=value;
  			}
  		}
  		return Math.min(sum,runModel.clientCount);
  	}
  
  	/**
  	 * bertrgt den Teil der allgemeinen Daten, der voraussetzt, dass sich bereits Stationen im Laufzeit-Modell befinden, vom Editor- in das Laufzeit-Modell.
  	 * @param editModel	Editor-Modell dem die Daten entnommen werden soll
  	 * @param runModel	Laufzeit-Modell in das die entsprechenden Daten eingetragen werden sollen
  	 * @return	Liefert im Erfolgsfall <code>null</code>, sonst eine Fehlermeldung
  	 * @see #getRunModel(EditModel, String, boolean, boolean)
  	 */
  	private static String initGeneralData2(final EditModel editModel, final RunModel runModel) {
  		/* Evtl. treffen weniger Kunden ein, als eingestellt ist (nmlich wenn nur Tabellenquellen verwendet werden). */
  		runModel.realArrivingClientCount=getArrivingRealClientCount(runModel);
  
  		/* Abbruchbedingung */
  		if (editModel.useTerminationCondition && !editModel.terminationCondition.isBlank()) {
  			runModel.terminationCondition=new ExpressionMultiEval(runModel.variableNames,editModel.userFunctions);
  			final int error=runModel.terminationCondition.parse(editModel.terminationCondition);
  			if (error>=0) return String.format(Language.tr("Simulation.Creator.InvalidTerminationCondition"),editModel.terminationCondition,error+1);
  		} else {
  			runModel.terminationCondition=null;
  		}
  
  		/* Abbruchzeit */
  		if (editModel.useFinishTime) {
  			runModel.terminationTime=FastMath.max(0,editModel.finishTime);
  		} else {
  			runModel.terminationTime=-1;
  		}
  
  		/* Abbruch ber Konfidenzintervall */
  		if (editModel.useFinishConfidence && editModel.finishConfidenceHalfWidth>0 && editModel.finishConfidenceLevel>0) {
  			runModel.terminationWaitingTimeConfidenceHalfWidth=editModel.finishConfidenceHalfWidth;
  			runModel.terminationWaitingTimeConfidenceLevel=editModel.finishConfidenceLevel;
  		} else {
  			runModel.terminationWaitingTimeConfidenceHalfWidth=-1;
  			runModel.terminationWaitingTimeConfidenceLevel=-1;
  		}
  
  		/* Seed fr Zufallszahlengenerator */
  		runModel.randomGeneratorMode=editModel.randomMode;
  		runModel.useFixedSeed=editModel.useFixedSeed;
  		runModel.fixedSeed=editModel.fixedSeed;
  
  		/* Bestimmung der Autokorrelation */
  		runModel.correlationRange=editModel.correlationRange;
  		runModel.correlationMode=editModel.correlationMode;
  
  		/* Batch-Means */
  		runModel.batchMeansSize=editModel.batchMeansSize;
  
  		/* Erfassung individueller Wartezeiten */
  		runModel.collectWaitingTimes=editModel.collectWaitingTimes;
  
  		/* Sequenzen - Stationen verdrahten */
  		for (int i=0;i<runModel.sequenceStepStationNames.length;i++) {
  			for (int j=0;j<runModel.sequenceStepStationNames[i].length;j++) {
  				final Integer I=runModel.namesToIDs.get(runModel.sequenceStepStationNames[i][j]);
  				if (I==null) return String.format(Language.tr("Simulation.Creator.SequenceUnknownStation"),runModel.sequenceNames[i],j+1,runModel.sequenceStepStationNames[i][j]);
  				runModel.sequenceStepStationIDs[i][j]=I.intValue();
  			}
  		}
  
  		/* Erfassungsbereich fr Verteilungen */
  		runModel.distributionRecordHours=editModel.distributionRecordHours;
  		runModel.stateRecordSize=editModel.stateRecordSize;
  		runModel.distributionRecordClientDataValues=editModel.distributionRecordClientDataValues;
  
  		/* Welford-basierte Varianzerfassung */
  		runModel.useWelford=editModel.useWelford;
  
  		return null;
  	}
  
  	/**
  	 * Ist bei einer Kundenquelle die Anzahl an Anknften limitiert?
  	 * @param record	Kundenquellen-Datensatz
  	 * @return	Liefert <code>true</code>, wenn entweder nur eine fest endliche Anzahl an Kunden eintreffen soll oder eine feste endliche Anzahl an Batchen (die ihrerseitg begrenzt gro sind) eintreffen soll
  	 */
  	private static boolean isLimitedSource(final ModelElementSourceRecord record) {
  		return (record.getMaxArrivalClientCount()>0 || record.getMaxArrivalCount()>0);
  	}
  
  	/**
  	 * Gibt an, ob die bertragung vom Editor- zum Laufzeit-Element
  	 * fr das angegebene Element im Hintergrund erfolgen kann.
  	 * @param element	Zu prfendes Element
  	 * @return	Liefert <code>true</code>, wenn die bertragung im Hintergrund erfolgen kann
  	 */
  	private static boolean runInBackgroundThread(final ModelElementBox element) {
  		if (element instanceof ElementWithScript) return true;
  		if (element instanceof ModelElementSourceTable) return true;
  		return false;
  	}
  
  	/**
  	 * Ermittelt aus der Fehlermeldung selbst weitere Fehler-Flags
  	 * @param errorMessage	Fehlermeldung
  	 * @return	Menge mit optionalen zustzlichen Fehler-Flags
  	 * @see AdditionalPrepareErrorInfo
  	 */
  	private static Set<StartAnySimulator.AdditionalPrepareErrorInfo> generateAdditionalScriptErrorInfo(final String errorMessage) {
  		if (errorMessage.contains(Language.tr("Simulation.Java.Error.NoCompiler.Internal"))) {
  			return new HashSet<>(Arrays.asList(StartAnySimulator.AdditionalPrepareErrorInfo.NO_COMPILER));
  		}
  		return Collections.emptySet();
  	}
  
  	/**
  	 * Zhler fr die Nummerierung der Hintergrund-Kompiler-Threads
  	 */
  	private static int prepareThreadNr=0;
  
  	/**
  	 * bertrgt die Stationen aus dem Editor-Modell in das Laufzeit-Modell.
  	 * @param editModel	Editor-Modell dem die Daten entnommen werden soll
  	 * @param runModel	Laufzeit-Modell in das die entsprechenden Daten eingetragen werden sollen
  	 * @param testOnly	Wird hier <code>true</code> bergeben, so werden externe Datenquellen nicht wirklich geladen
  	 * @param allowBackgroundProcessing	Darf die Vorbereitung von benutzerdefiniertem Java-Code und von externen Tabellenquelle in eigene Threads ausgelagert werden?
  	 * @return	Liefert im Erfolgsfall <code>null</code>, sonst eine Fehlermeldung
  	 * @see #getRunModel(EditModel, String, boolean, boolean)
  	 */
  	private static StartAnySimulator.PrepareError initElementsData(final EditModel editModel, final RunModel runModel, final boolean testOnly, final boolean allowBackgroundProcessing) {
  		final RunModelCreator creator=new RunModelCreator(editModel,runModel,testOnly);
  		final List<ModelElement> elements=new ArrayList<>(editModel.surface.getElements());
  
  		/* Skript-Elemente auf der Hauptebene verarbeiten */
  		prepareThreadNr=0;
  		final int coreCount=Runtime.getRuntime().availableProcessors();
  		final int maxThreadsByMemory=(int)Math.max(1,Runtime.getRuntime().maxMemory()/1024/1024/100); /* min. 100 MB pro Thread */
  		final int threadCount=Math.min(coreCount,maxThreadsByMemory);
  		final ThreadPoolExecutor executorPool=new ThreadPoolExecutor(0,threadCount,2,TimeUnit.SECONDS,new LinkedBlockingQueue<>(),(ThreadFactory)r-> { /* Da die Methode mehrfach parallel aufgerufen werden kann, muss der ThreadPool eine lokale Variable bleiben. */
  			prepareThreadNr++;
  			return new Thread(r,"Prepare model for simulation "+prepareThreadNr);
  		});
  		final List<Future<StartAnySimulator.PrepareError>> scriptProcessor=new ArrayList<>();
  
  		try {
  
  			/* Normale Elemente verarbeiten */
  			boolean hasSource=false;
  			boolean hasDispose=false;
  			boolean allSourcesLimited=true;
  			for (ModelElement element : elements) {
  				if (element instanceof ModelElementBox) {
  					final ModelElementBox boxElement=(ModelElementBox)element;
  					if (!boxElement.inputConnected()) continue; /* Keine einlaufende Ecke in Element -> kann ignoriert werden */
  
  					if (element instanceof ModelElementSource) {
  						hasSource=true;
  						allSourcesLimited=allSourcesLimited && isLimitedSource(((ModelElementSource)element).getRecord());
  					}
  					if (element instanceof ModelElementAnimationConnect) {
  						runModel.isAnimation=true;
  						runModel.animationConnect=(ModelElementAnimationConnect)element;
  					}
  					if (element instanceof ModelElementSourceMulti && !((ModelElementSourceMulti)element).getRecords().isEmpty()) {
  						hasSource=true;
  						for (ModelElementSourceRecord record: ((ModelElementSourceMulti)element).getRecords()) {
  							if (!record.isActive()) continue;
  							allSourcesLimited=allSourcesLimited && isLimitedSource(record);
  						}
  					}
  					if (element instanceof ModelElementSourceTable) hasSource=true;
  					if (element instanceof ModelElementSourceDB) hasSource=true;
  					if (element instanceof ModelElementSourceDDE) hasSource=true;
  					if (element instanceof ModelElementDispose) hasDispose=true;
  					if (element instanceof ModelElementDisposeWithTable) hasDispose=true;
  
  					if (allowBackgroundProcessing && !testOnly && runInBackgroundThread(boxElement)) {
  						scriptProcessor.add(executorPool.submit(()->{
  							final String err=creator.addElement(boxElement);
  							if (err==null) return null;
  							return new StartAnySimulator.PrepareError(err,boxElement.getId(),generateAdditionalScriptErrorInfo(err));
  						}));
  					} else {
  						final String error=creator.addElement(boxElement);
  						if (error!=null) return new StartAnySimulator.PrepareError(error,boxElement.getId(),generateAdditionalScriptErrorInfo(error));
  					}
  
  					if (element instanceof ModelElementSub) {
  						final ModelElementSub sub=(ModelElementSub)element;
  						final List<ModelElement> subElements=sub.getSubSurfaceReadOnly().getElements();
  						for (ModelElement subElement: subElements) if (subElement instanceof ModelElementBox) {
  							final ModelElementBox subBox=(ModelElementBox)subElement;
  							if (allowBackgroundProcessing && !testOnly && runInBackgroundThread(subBox)) {
  								scriptProcessor.add(executorPool.submit(()->{
  									final String err=creator.addElement(subBox,sub);
  									if (err==null) return null;
  									return new StartAnySimulator.PrepareError(err,subBox.getId(),generateAdditionalScriptErrorInfo(err));
  								}));
  							} else {
  								final String error=creator.addElement(subBox,sub);
  								if (error!=null) return new StartAnySimulator.PrepareError(error,subBox.getId(),generateAdditionalScriptErrorInfo(error));
  							}
  						}
  					}
  				} else {
  					if ((element instanceof InteractiveElement) && (element instanceof ModelElementPosition)) {
  						final String error=creator.addElement((ModelElementPosition)element);
  						if (error!=null) return new StartAnySimulator.PrepareError(error,element.getId(),generateAdditionalScriptErrorInfo(error));
  					}
  				}
  			}
  
  			/* Sind Eingang und Ausgang vorhanden? */
  			if (!hasSource) return new StartAnySimulator.PrepareError(Language.tr("Simulation.Creator.NoSource"),-1);
  			if (!hasDispose) {
  				if (!allSourcesLimited || (!editModel.useFinishTime && !editModel.useTerminationCondition)) return new StartAnySimulator.PrepareError(Language.tr("Simulation.Creator.NoDispose"),-1);
  			}
  
  			/* Hintergrundverarbeitungen abschlieen */
  			for (Future<StartAnySimulator.PrepareError> future: scriptProcessor) try {
  				final StartAnySimulator.PrepareError error=future.get();
  				if (error!=null) return error;
  			} catch (InterruptedException|ExecutionException e) {
  				return new StartAnySimulator.PrepareError(e.getMessage(),-1);
  			}
  
  		} finally {
  			executorPool.shutdown();
  		}
  
  		/* Verknpfungen umstellen von IDs auf Referenzen */
  		int maxID=0;
  		for (Map.Entry<Integer,RunElement> entry : runModel.elements.entrySet()) {
  			final RunElement element=entry.getValue();
  			if (element.id<0) return new StartAnySimulator.PrepareError(String.format(Language.tr("Simulation.Creator.NegativeID"),element.getClass().getName()),element.id);
  			if (element.id>maxID) maxID=element.id;
  			element.prepareRun(runModel);
  		}
  
  		/* Direkter Zugriff auf die Elemente ber ihre IDs */
  		runModel.elementsFast=new RunElement[maxID+1];
  		for (int i=0;i<=maxID;i++) runModel.elementsFast[i]=runModel.elements.get(i);
  
  		return null;
  	}
  
  	/**
  	 * bertrgt die Daten zur Laufzeitstatistik-Erfassung vom Editor- in das Laufzeit-Modell.
  	 * @param editModel	Editor-Modell dem die Daten entnommen werden soll
  	 * @param runModel	Laufzeit-Modell in das die entsprechenden Daten eingetragen werden sollen
  	 * @return	Liefert im Erfolgsfall <code>null</code>, sonst eine Fehlermeldung
  	 * @see #getRunModel(EditModel, String, boolean, boolean)
  	 */
  	private static String initAdditionalStatistics(final EditModel editModel, final RunModel runModel) {
  		if (!editModel.longRunStatistics.isActive()) return null;
  
  		final RunModelCreator creator=new RunModelCreator(editModel,runModel,false);
  		return creator.addLongRunStatistic();
  	}
  
  	/**
  	 * Wandelt ein <code>EditModel</code> in ein <code>RunModel</code> um. Dabei wird das Modell auf Konsistenz geprft
  	 * und alle notwendigen Verknpfungen werden hergestellt.
  	 * @param editModel	Editor-Modell, welches in ein Laufzeit-Modell umgewandelt werden soll
  	 * @param modelPath	Pfad zur zugehrigen Modelldatei (als Basis fr relative Pfade in Ausgabeelementen)
  	 * @param testOnly	Wird hier <code>true</code> bergeben, so werden externe Datenquellen nicht wirklich geladen
  	 * @param allowBackgroundProcessing	Darf die Vorbereitung von benutzerdefiniertem Java-Code und von externen Tabellenquelle in eigene Threads ausgelagert werden?
  	 * @return	Gibt im Erfolgsfall ein Objekt vom Typ <code>RunModel</code> zurck, sonst <code>PrepareError</code>-Objekt mit einer Fehlermeldung.
  	 * @see EditModel
  	 */
  	public static Object getRunModel(final EditModel editModel, final String modelPath, final boolean testOnly, final boolean allowBackgroundProcessing) {
  		RunModel runModel=new RunModel();
  
  		String error;
  		error=initVariables(editModel,runModel); if (error!=null) return new StartAnySimulator.PrepareError(error,-1);
  		error=initGeneralData(editModel,modelPath,runModel); if (error!=null) return new StartAnySimulator.PrepareError(error,-1);
  		final StartAnySimulator.PrepareError prepareError=initElementsData(editModel,runModel,testOnly,allowBackgroundProcessing); if (prepareError!=null) return prepareError;
  		error=initGeneralData2(editModel,runModel); if (error!=null) return new StartAnySimulator.PrepareError(error,-1); /* Hier brauchen wir die Variablennamen und die werden erst in initElementsData gesetzt. */
  		error=initAdditionalStatistics(editModel,runModel); if (error!=null) return new StartAnySimulator.PrepareError(error,-1);
  
  		return runModel;
  	}
  
  	/**
  	 * Liefert die Nummer eines Fertigungsplans
  	 * @param name	Name des Fertigungsplans
  	 * @return	Nummer des Plans (oder -1, wenn kein Plan mit dem Namen gefunden wurde)
  	 * @see RunModel#sequenceNames
  	 */
  	public int getSequenceNr(final String name) {
  		if (name.isEmpty()) return -1;
  		for (int i=0;i<sequenceNames.length;i++) if (sequenceNames[i].equalsIgnoreCase(name)) return i;
  		return -1;
  	}
  }