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 statistics;
  
  import java.util.Arrays;
  
  import org.apache.commons.math3.distribution.TDistribution;
  import org.w3c.dom.Element;
  
  import mathtools.NumberTools;
  import mathtools.distribution.DataDistributionImpl;
  
  /**
   * Statistik-Klasse, die erfasst, wie lange sich das System in einem bestimmten, durch einen
   * Integer-Zahlenwert benannten Zustand befunden hat.<br>
   * Die Zhlung wird ber die Funktion {@link StatisticsTimePerformanceIndicator#set(double, int)} realisiert.<br>
   * Sollen hingegen durch einen (String-)Namen definierte Zustnde erfasst werden,
   * so kann dafr die Klasse {@link StatisticsStateTimePerformanceIndicator} verwendet werden.
   * @author Alexander Herzog
   * @version 3.6
   */
  public final class StatisticsTimePerformanceIndicator extends StatisticsPerformanceIndicator implements Cloneable {
  	/**
  	 * Hchster erfasster Zustandswert (Array luft von 0 bis <code>MAX_STATE</code>, also <code>MAX_STATE+1</code> Werte)
  	 */
  	public static final int DEFAULT_MAX_STATE=2048*1024;
  
  	/**
  	 * Hchste zulssige Einstellung fr <code>maxState</code>
  	 * @see #maxState
  	 */
  	public static final int MAX_MAX_STATE=2048*1024;
  
  	/** Fehlermeldung, wenn der Inhalt des XML-Elements nicht gelesen werden konnte. */
  	public static String xmlLoadError="Die in dem Element \"%s\" angegebene Verteilung ist ungltig.";
  	/** XML-Attribut fr "Start" */
  	public static String[] xmlNameStart=new String[]{"Start"};
  	/** Fehlermeldung, wenn das "Start"-Attribut nicht gelesen werden konnte. */
  	public static String xmlNameStartError="Das Start-Attribut im \"%s\"-Element muss eine Zahl sein, ist aber \"%s\".";
  	/** XML-Attribut fr "Summe" */
  	public static String xmlNameSum="Summe";
  	/** XML-Attribut fr "WerteSumme" */
  	public static String[] xmlNameValues=new String[]{"WerteSumme"};
  	/** Fehlermeldung, wenn das "WerteSumme"-Attribut nicht gelesen werden konnte. */
  	public static String xmlNameValuesError="Das WerteSumme-Attribut im \"%s\"-Element muss eine Zahl sein, ist aber \"%s\".";
  	/** XML-Attribut fr "WerteSumme2" */
  	public static String[] xmlNameValuesSquared=new String[]{"WerteSumme2"};
  	/** Fehlermeldung, wenn das "WerteSumme2"-Attribut nicht gelesen werden konnte. */
  	public static String xmlNameValuesSquaredError="Das WerteSumme2-Attribut im \"%s\"-Element muss eine nicht-negative Zahl sein, ist aber \"%s\".";
  	/** XML-Attribut fr "Summe3" (=Summe der kubischen Werte) */
  	public static String[] xmlNameValuesCubic=new String[]{"Summe3"};
  	/** Fehlermeldung, wenn das "Summe3"-Attribut nicht gelesen werden konnte. */
  	public static String xmlNameValuesCubicError="Das Summe3-Attribut im \"%s\"-Element muss eine Zahl sein, ist aber \"%s\".";
  	/** XML-Attribut fr "Summe4" (=Summe der mit 4 potenzierten Werte) */
  	public static String[] xmlNameValuesQuartic=new String[]{"Summe4"};
  	/** Fehlermeldung, wenn das "Summe4"-Attribut nicht gelesen werden konnte. */
  	public static String xmlNameValuesQuarticError="Das Summe4-Attribut im \"%s\"-Element muss eine Zahl sein, ist aber \"%s\".";
  	/** XML-Attribut fr "Mittelwert" */
  	public static String xmlNameMean="Mittelwert";
  	/** XML-Attribut fr "Standardabweichung" */
  	public static String xmlNameSD="Standardabweichung";
  	/** XML-Attribut fr "Variationskoeffizient" */
  	public static String xmlNameCV="Variationskoeffizient";
  	/** XML-Attribut fr "Schiefe" */
  	public static String[] xmlNameSk=new String[]{"Schiefe"};
  	/** XML-Attribut fr "Kurt" */
  	public static String[] xmlNameKurt=new String[]{"Kurt"};
  	/** XML-Attribut fr "Minimum" */
  	public static String[] xmlNameMin=new String[]{"Minimum"};
  	/** Fehlermeldung, wenn das "Minimum"-Attribut nicht gelesen werden konnte. */
  	public static String xmlNameMinError="Das Minimum-Attribut im \"%s\"-Element muss eine Zahl sein, ist aber \"%s\".";
  	/** XML-Attribut fr "Maximum" */
  	public static String[] xmlNameMax=new String[]{"Maximum"};
  	/** Fehlermeldung, wenn das "Maximum"-Attribut nicht gelesen werden konnte. */
  	public static String xmlNameMaxError="Das Maximum-Attribut im \"%s\"-Element muss eine Zahl sein, ist aber \"%s\".";
  	/** XML-Attribut fr "Quantil" */
  	public static String xmlNameQuantil="Quantil";
  
  	/**
  	 * Quantile, die aus der Hufigkeitsverteilung berechnet und in der xml-Datei gespeichert werden
  	 */
  	public static final double[] storeQuantilValues=new double[] {0.10,0.25,0.5,0.75,0.9};
  
  	/**
  	 * Hchster erfasster Zustandswert (Array luft von 0 bis <code>maxState</code>, also <code> maxState+1</code> Werte)
  	 * @see #stateTime
  	 */
  	public final int maxState;
  
  	/**
  	 * Zeitpunkt der letzten nderung des Zustands des Systems.
  	 */
  	private double lastTime=0.0;
  
  	/**
  	 * Aktueller Zustand des Systems.
  	 */
  	private int lastState=0;
  
  	/**
  	 * Zhlt, wie lange sich das System in welchem Zustand befinden hat.
  	 */
  	private double[] stateTime=null;
  
  	/**
  	 * Zhlt die Zeit, die sich das System im Zustand 0 befunden hat (solange stateTime==null ist gltig)
  	 */
  	private double time0=-1;
  
  	/**
  	 * Zhlt die Zeit, in der sich das System im Zustand timeMaxState befunden hat (solange stateTime==null ist gltig)
  	 */
  	private double timeMax=-1;
  
  	/**
  	 * Zustand dessen Zeit in timeMax erfasst wird
  	 */
  	private int timeMaxState=-1;
  
  	/**
  	 * Summe der erfassten Zeiten (d.h. Summe ber {@link StatisticsTimePerformanceIndicator#stateTime})
  	 */
  	private double sum=0;
  
  	/**
  	 * Startzeitpunkt der Erfassung
  	 */
  	private double start=0;
  
  	/**
  	 * Minimaler Zustand in dem sich das System eine Zeitdauer >0 befunden hat
  	 */
  	private int min=-1;
  
  	/**
  	 * Maximaler Zustand in dem sich das System eine Zeitdauer >0 befunden hat
  	 */
  	private int max=-1;
  
  	/**
  	 * Summe der Zustnde gewichtet mit der jeweiligen Zeitdauer
  	 */
  	private double valueSum=0;
  
  	/**
  	 * Summe der quadrierten Zustnde gewichtet mit der jeweiligen Zeitdauer
  	 */
  	private double valueSumSquared=0;
  
  	/**
  	 * Summe der mit 3 potenzierten Zustnde gewichtet mit der jeweiligen Zeitdauer
  	 */
  	private double valueSumCubic=0;
  
  	/**
  	 * Summe der mit 4 potenzierten Zustnde gewichtet mit der jeweiligen Zeitdauer
  	 */
  	private double valueSumQuartic=0;
  
  	/**
  	 * Ergebnis der letzten Berechnung des Durchschnitts (-1, wenn sich die Werte seit der letzten Berechnung verndert haben und eine neue Rechnung notwendig ist)
  	 */
  	private double lastTimeMean=-1;
  
  	/**
  	 * Gibt an, ob der Zhler gerade auf eine explizite Startzeit gesetzt wurde.
  	 * @see StatisticsTimePerformanceIndicator#setTime
  	 */
  	private boolean explicitTimeInit=false;
  
  	/**
  	 * Ist eine Initialisierung ber eine Zeitangabe erfolgt?
  	 * @see #setTime(double)
  	 * @see #explicitTimeInit
  	 * @see #set(double, int)
  	 */
  	private boolean wasExplicitTimeInit=false;
  
  	/**
  	 * Anzahl an erfassten Teil-Simulationslufen
  	 */
  	private int runCount;
  
  	/**
  	 * Summe der Werte pro erfasstem Teil-Simulationslauf
  	 */
  	private double runSum;
  
  	/**
  	 * Summe der quadrierten Werte pro erfasstem Teil-Simulationslauf
  	 */
  	private double runSum2;
  
  	/**
  	 * Varianz zwischen den Teil-Simulationslauf Mittelwerten<br>
  	 * Wird von {@link #getRunVar()} berechnet.
  	 * @see #getRunVar()
  	 */
  	private double runVar;
  
  	/**
  	 * Konstruktor der Klasse <code>StatisticsTimePerformanceIndicator</code>
  	 * @param xmlNodeNames	Name des xml-Knotens, in dem die Daten gespeichert werden sollen
  	 * @param maxState	Welcher Zustand (z.B. Anzahl an Kunden im System) soll maximal erfasst werden?
  	 */
  	public StatisticsTimePerformanceIndicator(final String[] xmlNodeNames, final int maxState) {
  		super(xmlNodeNames);
  		this.maxState=Math.max(2,Math.min(maxState,MAX_MAX_STATE));
  		reset();
  	}
  
  	/**
  	 * Konstruktor der Klasse <code>StatisticsTimePerformanceIndicator</code>
  	 * @param xmlNodeNames	Name des xml-Knotens, in dem die Daten gespeichert werden sollen
  	 */
  	public StatisticsTimePerformanceIndicator(final String[] xmlNodeNames) {
  		this(xmlNodeNames,DEFAULT_MAX_STATE);
  	}
  
  	/**
  	 * Wurden bislang nur {@link #time0} und {@link #timeMax} erfasst, so kann ber
  	 * diese Funktion das vollstndige {@link #stateTime}-Array aufgespannt werden.
  	 * @see #set(double, int)
  	 */
  	private void forceExpandStateTime() {
  		if (stateTime==null) {
  			final int size=Math.min(maxState,Math.max(timeMaxState,10))+1;
  			stateTime=new double[size];
  			if (time0>0) stateTime[0]=time0;
  			if (timeMaxState>0 && timeMax>0) stateTime[Math.min(stateTime.length-1,timeMaxState)]=timeMax;
  			time0=-1;
  			timeMax=-1;
  			timeMaxState=-1;
  		}
  	}
  
  	/**
  	 * Erfasst eine Zustandsnderung
  	 * @param time	Aktuelle Zeit
  	 * @param newState	Neuer Zustand (muss eine nicht-negative Zahl sein)
  	 */
  	public void set(final double time, final int newState) {
  		if (time!=lastTime || explicitTimeInit) {
  			int max=(lastState>newState)?lastState:newState;
  			if (max<1) max=1;
  			if (max>maxState) max=maxState;
  			final boolean init=(lastTime<=0.0d && stateTime==null && !wasExplicitTimeInit) || explicitTimeInit;
  
  			if (explicitTimeInit) wasExplicitTimeInit=true;
  			explicitTimeInit=false;
  			if (stateTime==null && time0<0 && timeMax<0) {
  				this.min=-1;
  				this.max=-1;
  				sum=0;
  				valueSum=0;
  				valueSumSquared=0;
  				valueSumCubic=0;
  				valueSumQuartic=0;
  			}
  
  			if (!init) {
  				final double add=time-lastTime;
  				if (stateTime==null) {
  					if (lastState==0) {
  						time0=(time0>=0.0d)?(time0+add):add;
  					} else {
  						if (timeMaxState==-1 || timeMaxState==lastState) {
  							timeMaxState=lastState;
  							timeMax=(timeMax>=0.0d)?(timeMax+add):add;
  						} else {
  							forceExpandStateTime();
  						}
  					}
  				}
  
  				if (stateTime!=null) {
  					if (stateTime.length<=max) stateTime=Arrays.copyOf(stateTime,Math.min(maxState+1,(max+1)*2));
  					stateTime[(stateTime.length-1<lastState)?stateTime.length-1:lastState]+=add;
  				}
  
  				if (lastState<this.min || this.min==-1) this.min=lastState;
  				if (lastState>this.max || this.max==-1) this.max=lastState;
  				sum+=add;
  				if (lastState>0) {
  					final double addTimesLastState=add*lastState;
  					final double squared=lastState*lastState;
  					valueSum+=addTimesLastState;
  					valueSumSquared+=add*squared;
  					valueSumCubic+=addTimesLastState*squared;
  					valueSumQuartic+=add*squared*squared;
  				}
  			} else {
  				start=time;
  			}
  
  			lastTime=time;
  		}
  
  		lastState=(newState>=0)?newState:0;
  
  		lastTimeMean=-1.0d;
  	}
  
  	/**
  	 * Liefert den zuletzt per <code>set()</code> eingestellten Zustand
  	 * @return	Aktuell eingestellter Zustand
  	 * @see #set(double, int)
  	 */
  	public int getCurrentState() {
  		return lastState;
  	}
  
  	/**
  	 * Stellt die aktuelle Systemzeit ein (z.B. wenn der Beginn der Simulation nicht zum Zeitpunkt 0 erfolgt)
  	 * @param time	Neue aktuelle Systemzeit
  	 */
  	public void setTime(final double time) {
  		lastTime=time;
  		lastTimeMean=-1;
  		explicitTimeInit=true;
  		time0=-1;
  		timeMax=-1;
  		timeMaxState=-1;
  	}
  
  	@Override
  	public void add(StatisticsPerformanceIndicator moreStatistics) {
  		if (!(moreStatistics instanceof StatisticsTimePerformanceIndicator)) return;
  		StatisticsTimePerformanceIndicator moreCountStatistics=(StatisticsTimePerformanceIndicator)moreStatistics;
  
  		moreCountStatistics.forceExpandStateTime();
  		if (moreCountStatistics.stateTime!=null) {
  			if (stateTime==null) forceExpandStateTime();
  			if (stateTime.length<moreCountStatistics.stateTime.length) stateTime=Arrays.copyOf(stateTime,moreCountStatistics.stateTime.length);
  			for (int i=0;i<moreCountStatistics.stateTime.length;i++) stateTime[i]=stateTime[i]+moreCountStatistics.stateTime[i];
  		}
  
  		if (moreCountStatistics.min>=0) {
  			if (min>=0) min=Math.min(min,moreCountStatistics.min); else min=moreCountStatistics.min; /* Das brauchen wir nur bei min. Bei max wird der Fall max==-1 automatisch durch Math.max(...) erledigt. */
  		}
  		if (moreCountStatistics.max>=0) {
  			max=Math.max(max,moreCountStatistics.max);
  		}
  		if (start==0) {
  			start=moreCountStatistics.start;
  		} else {
  			if (moreCountStatistics.start>0) start=Math.min(start,moreCountStatistics.start);
  		}
  		sum+=moreCountStatistics.sum;
  		valueSum+=moreCountStatistics.valueSum;
  		valueSumSquared+=moreCountStatistics.valueSumSquared;
  		valueSumCubic+=moreCountStatistics.valueSumCubic;
  		valueSumQuartic+=moreCountStatistics.valueSumQuartic;
  
  		lastTimeMean=-1;
  
  		/* Daten zu einzelnen Teil-Simulationslufen */
  		runCount+=moreCountStatistics.runCount;
  		runSum+=moreCountStatistics.runSum;
  		runSum2+=moreCountStatistics.runSum2;
  		if (moreCountStatistics.runVar>0) runVar=moreCountStatistics.runVar;
  	}
  
  	/**
  	 * Setzt alle Teil-Kenngren auf 0 zurck.
  	 */
  	@Override
  	public void reset() {
  		lastState=0;
  		lastTime=0;
  		stateTime=null;
  		time0=-1;
  		timeMax=-1;
  		timeMaxState=-1;
  
  		min=-1;
  		max=-1;
  		start=0;
  		sum=0;
  		valueSum=0;
  		valueSumSquared=0;
  		valueSumCubic=0;
  		valueSumQuartic=0;
  
  		lastTimeMean=-1;
  
  		/* Daten zu einzelnen Teil-Simulationslufen */
  		runCount=0;
  		runSum=0;
  		runSum2=0;
  		runVar=0;
  	}
  
  	/**
  	 * Kopiert die Daten eines anderen Statistik-Objektes in dieses
  	 * @param indicator	Objekt, aus dem die Daten kopiert werden sollen
  	 */
  	@Override
  	protected void copyDataFrom(final StatisticsPerformanceIndicator indicator) {
  		if (!(indicator instanceof StatisticsTimePerformanceIndicator)) return;
  		final StatisticsTimePerformanceIndicator time=(StatisticsTimePerformanceIndicator)indicator;
  
  		lastState=time.lastState;
  		lastTime=time.lastTime;
  		if (time.stateTime!=null) {
  			stateTime=Arrays.copyOf(time.stateTime,time.stateTime.length);
  		}
  		time0=time.time0;
  		timeMax=time.timeMax;
  		timeMaxState=time.timeMaxState;
  
  		min=time.min;
  		max=time.max;
  		start=time.start;
  		sum=time.sum;
  		valueSum=time.valueSum;
  		valueSumSquared=time.valueSumSquared;
  		valueSumCubic=time.valueSumCubic;
  		valueSumQuartic=time.valueSumQuartic;
  
  		lastTimeMean=-1;
  
  		/* Daten zu einzelnen Teil-Simulationslufen */
  		runCount=time.runCount;
  		runSum=time.runSum;
  		runSum2=time.runSum2;
  		runVar=time.runVar;
  	}
  
  	/**
  	 * Legt eine Kopie des Objekts an.
  	 * @return Kopie des Statistik-Objektes
  	 */
  	@Override
  	public StatisticsTimePerformanceIndicator clone() {
  		final StatisticsTimePerformanceIndicator indicator=new StatisticsTimePerformanceIndicator(xmlNodeNames,maxState);
  		indicator.copyDataFrom(this);
  		return indicator;
  	}
  
  	/**
  	 * Legt eine Kopie des Objekts an, bernimmt aber keine Daten
  	 * (da das Ausgangsobjekt noch leer ist).
  	 * @return Kopie des Statistik-Objektes
  	 */
  	@Override
  	public StatisticsTimePerformanceIndicator cloneEmpty() {
  		return new StatisticsTimePerformanceIndicator(xmlNodeNames,maxState);
  	}
  
  	/**
  	 * Liefert die Verteilung zurck, wie lange sich das System in welchem Zustand befinden hat
  	 * @return	Zeit-Verteilung ber die Systemzustnde
  	 */
  	public DataDistributionImpl getDistribution() {
  		if (stateTime==null && time0<0 && timeMax<0) return new DataDistributionImpl(1,new double[]{0.0});
  		forceExpandStateTime();
  		return new DataDistributionImpl(stateTime.length,stateTime);
  		/* stateTime.length ist die richtige Gre, sonst skaliert DataDistributionImpl und getMean() und Co. liefern verzerrte Werte. */
  	}
  
  	/**
  	 * Leeres Verteilungs-Objekt welches von {@link #getReadOnlyDistribution()}
  	 * zurckgeliefert wird, wenn noch keine Daten erfasst wurden.
  	 * @see #getReadOnlyDistribution()
  	 */
  	private static DataDistributionImpl emptyDistribution=new DataDistributionImpl(1,new double[]{0.0});
  
  	/**
  	 * Zuletzt per {@link #getReadOnlyDistribution()} ausgelieferte Verteilung<br>
  	 * (Wird wiederverwendet, wenn sich die Daten seit dem letzten Aufruf nicht verndert haben.)
  	 * @see #getReadOnlyDistribution()
  	 */
  	private DataDistributionImpl readOnlyDistribution=null;
  
  	/**
  	 * Liefert die Verteilung zurck, wie lange sich das System in welchem Zustand befinden hat.
  	 * Verwendet dabei die Originaldaten, d.h. das <code>DataDistributionImpl</code>-Objekt darf danach nicht verndert werden (z.B. darf es nicht mit anderen Objekten zusammengefhrt werden)
  	 * @return	Zeit-Verteilung ber die Systemzustnde
  	 * @see StatisticsTimePerformanceIndicator#getDistribution
  	 */
  	public DataDistributionImpl getReadOnlyDistribution() {
  		if (stateTime==null && time0<0 && timeMax<0) return emptyDistribution;
  		forceExpandStateTime();
  		if (readOnlyDistribution==null || readOnlyDistribution.densityData!=stateTime) readOnlyDistribution=new DataDistributionImpl(stateTime.length,stateTime,true);
  		/* stateTime.length ist die richtige Gre, sonst skaliert DataDistributionImpl und getMean() und Co. liefern verzerrte Werte. */
  		return readOnlyDistribution;
  	}
  
  	/**
  	 * Liefert den Zeitpunkt der ersten Erfassung eines Wertes zurck.
  	 * @return	Zeitpunkt der ersten Erfassung eines Wertes zurck
  	 */
  	public double getStartTime() {
  		return start;
  	}
  
  	/**
  	 * Liefert die Summe der Zeiten der Systemzustnde zurck.
  	 * @return	Summe der Zeiten der Systemzustnde
  	 */
  	public double getSum() {
  		return sum;
  	}
  
  	/**
  	 * Liefert die normalisierte Hufigkeitsverteilung der Messreihe
  	 * (nur Dichtewerte, keine Verteilung; diese kann aber per {@link DataDistributionImpl#updateCumulativeDensity()} berechnet werden)
  	 * @return	Normalisierte Hufigkeitsverteilung der Messwerte in der Messreihe
  	 */
  	public DataDistributionImpl getNormalizedDistribution() {
  		final DataDistributionImpl normalized=getDistribution();
  		normalized.normalizeDensityOnly();
  		return normalized;
  	}
  
  	/**
  	 * Liefert die Laufzeit des Systems (Summe ber die Aufenthaltszeiten in allen Zustnden)
  	 * @return	Laufzeit des Systems
  	 */
  	public double getTimeSum() {
  		return getReadOnlyDistribution().sumAsStoredAsString();
  	}
  
  	/**
  	 * Liefert den Zustand, in dem sich das System im Mittel befunden hat (z.B. mittlere Anzahl an Kunden im System, wenn die Anzahl an Kunden im System erfasst wird)
  	 * @return	Mittlerer Zustand
  	 */
  	public double getTimeMean() {
  		if (lastTimeMean<0) {
  			if (sum>0) lastTimeMean=valueSum/sum; else lastTimeMean=0;
  		}
  		/* So wrde ggf. abgeschnitten werden: getReadOnlyDistribution().getMean(); */
  		return lastTimeMean;
  	}
  
  	/**
  	 * Berechnet ein Quantil der Messreihe aus der Hufigkeitsverteilung.
  	 * @param sum	Summe ber die Messreihe
  	 * @param p	Wert fr das Quantil
  	 * @return	Quantil der Messreihe
  	 * @see #getQuantil(double)
  	 */
  	private int getQuantil(final double sum, final double p) {
  		final double quantilSum=sum*Math.min(1.0,Math.max(0.0,p));
  		double partialSum=0;
  		for (int i=0;i<stateTime.length;i++) {
  			partialSum+=stateTime[i];
  			if (partialSum>=quantilSum) return i;
  		}
  		return 0;
  	}
  
  	/**
  	 * Berechnet ein Quantil der Messreihe aus der Hufigkeitsverteilung.
  	 * @param p	Wert fr das Quantil
  	 * @return	Quantil der Messreihe
  	 */
  	public int getQuantil(final double p) {
  		if (stateTime==null) {
  			return (timeMax>0)?timeMaxState:0;
  		}
  
  		double sum=0.0;
  		for (double value: stateTime) sum+=value;
  
  		return getQuantil(sum,p);
  	}
  
  	/**
  	 * Berechnet mehrere Quantile der Messreihe aus der Hufigkeitsverteilung.
  	 * @param p	Werte fr die Quantile
  	 * @return	Quantile der Messreihe
  	 */
  	public int[] getQuantil(final double[] p) {
  		if (p==null) return null;
  		if (p.length==0) return new int[0];
  
  		final int[] result=new int[p.length];
  
  		if (stateTime==null) {
  			Arrays.fill(result,(timeMax>0)?timeMaxState:0);
  		} else {
  			double sum=0.0;
  			for (double value: stateTime) sum+=value;
  			for (int i=0;i<p.length;i++) result[i]=getQuantil(sum,p[i]);
  		}
  
  		return result;
  	}
  
  	/**
  	 * Berechnet den Median der Messreihe aus der Hufigkeitsverteilung.
  	 * @return	Median der Messreihe
  	 */
  	public int getTimeMedian() {
  		return getQuantil(0.5);
  	}
  
  	/**
  	 * Liefert die Standardabweichung ber die Verteilung der Zeiten in den verschiedenen Zustnden
  	 * @return	Standardabweichung ber die Verteilung der Zeiten in den verschiedenen Zustnden
  	 */
  	public double getTimeSD() {
  		/* So wrde ggf. abgeschnitten werden: return getReadOnlyDistribution().getStandardDeviation(); */
  		if (sum<2) return 0;
  		final double v=valueSumSquared/(sum-1)-(valueSum*valueSum)/sum/(sum-1);
  		return StrictMath.sqrt(Math.max(0,v));
  	}
  
  	/**
  	 * Liefert die Varianz ber die Verteilung der Zeiten in den verschiedenen Zustnden
  	 * @return	Varianz ber die Verteilung der Zeiten in den verschiedenen Zustnden
  	 */
  	public double getTimeVar() {
  		/* So wrde ggf. abgeschnitten werden: double sd=getReadOnlyDistribution().getStandardDeviation(); return sd*sd; */
  		if (sum<2) return 0;
  		return Math.max(0,valueSumSquared/(sum-1)-(valueSum*valueSum)/sum/(sum-1));
  	}
  
  	/**
  	 * Liefert den Variationskoeffizienten ber die Verteilung der Zeiten in den verschiedenen Zustnden
  	 * @return	Variationskoeffizienten ber die Verteilung der Zeiten in den verschiedenen Zustnden
  	 */
  	public double getTimeCV() {
  		/* So wrde ggf. abgeschnitten werden: DataDistributionImpl dist=getReadOnlyDistribution(); return dist.getStandardDeviation()/FastMath.max(0.0001,FastMath.abs(dist.getMean())); */
  		double mean=getTimeMean();
  		return (mean>0)?(getTimeSD()/mean):0;
  	}
  
  	/**
  	 * Liefert die Schiefe ber die Verteilung der Zeiten in den verschiedenen Zustnden.
  	 * @return	Schiefe ber die Verteilung der Zeiten in den verschiedenen Zustnden
  	 */
  	public double getTimeSk() {
  		final double n=sum;
  		final double mean=getTimeMean();
  		final double sd=getTimeSD();
  		if (sum<3 || sd==0.0) return 0;
  
  		/* siehe: https://de.wikipedia.org/wiki/Schiefe_(Statistik) */
  		return n/(n-1)/(n-2)/Math.pow(sd,3)*(valueSumCubic-3*mean*valueSumSquared+2*n*Math.pow(mean,3));
  	}
  
  	/**
  	 * Berechnet den Exzess (von der Wlbung abgeleitetes Ma) ber die Verteilung der Zeiten in den verschiedenen Zustnden.
  	 * @return	Exzess ber die Verteilung der Zeiten in den verschiedenen Zustnden
  	 */
  	public double getTimeKurt() {
  		final double n=sum;
  		final double mean=getTimeMean();
  		final double sd=getTimeSD();
  		if (sum<4 || sd==0.0) return 0;
  
  		/* siehe: https://en.wikipedia.org/wiki/Kurtosis */
  		final double normDistComparision=3*Math.pow(n-1,2)/(n-2)/(n-3);
  		return n*(n+1)/(n-1)/(n-2)/(n-3)/Math.pow(sd,4)*(valueSumQuartic-4*mean*valueSumCubic+6*Math.pow(mean,2)*valueSumSquared-3*n*Math.pow(mean,4))-normDistComparision;
  	}
  
  	/**
  	 * Liefert den hchsten Zustand, in dem sich das System eine Zeit &gt;0 befunden hat
  	 * @return	Maximale Zustand, in dem sich das System eine positive Zeit lang befunden hat
  	 */
  	public int getTimeMax() {
  		if ((stateTime==null || sum==0) && time0<0 && timeMax<0) return 0;
  		/*
  		So wrde ggf. abgeschnitten werden:
  		for (int i=stateTime.length-1;i>=0;i--) if (stateTime[i]>0) return i;
  		return 0;
  		 */
  		return max;
  	}
  
  	/**
  	 * Liefert den niedrigsten Zustand, in dem sich das System eine Zeit &gt;0 befunden hat
  	 * @return	Minimaler Zustand, in dem sich das System eine positive Zeit lang befunden hat
  	 */
  	public int getTimeMin() {
  		if ((stateTime==null || sum==0) && time0<0 && timeMax<0) return 0;
  		/*
  		So wrde ggf. abgeschnitten werden:
  		for (int i=0;i<stateTime.length;i++) if (stateTime[i]>0) return i;
  		return stateTime.length-1;
  		 */
  		return min;
  	}
  
  	/**
  	 * Liefert den Zeitanteil, in dem sich das System in einem bestimmten Zustand befunden hat
  	 * @param state	Zustand, von dem der Zeitanteil abgefragt werden soll
  	 * @return	Zeitanteil, in dem sich das System in dem Zustand befunden hat
  	 */
  	public double getTimePartForState(int state) {
  		if (stateTime==null && time0<0 && timeMax<0) return 0;
  		if (stateTime==null) {
  			if (state<0) return 0;
  			if (state==0) {
  				if (timeMaxState<0) return (time0>0)?1:0;
  				if (time0+timeMax>0) return	time0/(time0+timeMax); else return 0;
  			}
  			if (state==timeMaxState) {
  				if (timeMax>0) return timeMax/(time0+timeMax); else return 0;
  			}
  			return 0;
  		} else {
  			if (state<0 || state>=stateTime.length) return 0.0;
  			return stateTime[state]/sum;
  		}
  	}
  
  	/**
  	 * Beendet einen Simulationslauf fr die Erfassung der
  	 * Konfidenzdaten ber mehrere Teil-Simulationslufe hinweg.
  	 */
  	public void finishRun() {
  		final double value=getTimeMean();
  		runCount++;
  		runSum+=value;
  		runSum2+=(value*value);
  	}
  
  	/**
  	 * Liefert die Anzahl an erfassten Teil-Simulationslufen.
  	 * @return	Anzahl an erfassten Teil-Simulationslufen
  	 */
  	public int getRunCount() {
  		return runCount;
  	}
  
  	/**
  	 * Liefert die Varianz zwischen den Teil-Simulationslufen.<br>
  	 * (Setzt voraus, dass das System Daten zu Teil-Simulationslufen aufgezeichnet hat.)
  	 * @return	Varianz zwischen den Teil-Simulationslufen
  	 */
  	public double getRunVar() {
  		if (runVar==0.0) {
  			if (runCount<2) return 0;
  			final double xMean=getTimeMean();
  			final int b=runCount;
  			runVar=1.0/b/(b-1)*(runSum2-2*xMean*runSum+b*xMean*xMean);
  		}
  		return Math.max(0,runVar); /* Um Rundungsprobleme zu vermeiden. */
  	}
  
  	/**
  	 * Liefert die Standardabweichung zwischen den Teil-Simulationslufen.<br>
  	 * (Setzt voraus, dass das System Daten zu Teil-Simulationslufen aufgezeichnet hat.)
  	 * @return	Standardabweichung zwischen den Teil-Simulationslufen
  	 */
  	public double getRunSD() {
  		return StrictMath.sqrt(getRunVar());
  	}
  
  	/**
  	 * Halbe Breite des Konfidenzintervalls fr den Mittelwert (unter Bercksichtigung der Teil-Simulationslufe)<br>
  	 * Das Konfidenzintervall geht dann von <code>getMean()-getRunConfidenceHalfWide(alpha)</code> bis <code>getMean()+getRunConfidenceHalfWide(alpha)</code>
  	 * @param alpha	Konfidenzniveau (z.B. alpha=0.05 oder alpha=0.01)
  	 * @return	Halbe Breite des Konfidenzintervalls
  	 */
  	public double getRunConfidenceHalfWide(final double alpha) {
  		if (min==max) return 0;
  		final int b=runCount;
  		if (b==0) return 0; /* Keine Lufe erfasst */
  		if (b==1) return 0; /* Sorry, aber TDistribution(0) geht auch nicht. */
  		final TDistribution dist=new TDistribution(b-1);
  		final double t=dist.inverseCumulativeProbability(1-alpha/2);
  		final double sd=getRunSD();
  		return t*sd; /* Division durch sqrt(b) steckt schon in getRunSD() */
  	}
  
  	/**
  	 * Halbe Breite des Konfidenzintervalls fr den Mittelwert (unter Bercksichtigung der Teil-Simulationslufe) (mehrere Konfidenzniveaus gleichzeitig)<br>
  	 * Das Konfidenzintervall geht dann von <code>getMean()-getRunConfidenceHalfWide(alpha)</code> bis <code>getMean()+getRunConfidenceHalfWide(alpha)</code>
  	 * @param alpha	Konfidenzniveaus (z.B. alpha=0.05 oder alpha=0.01)
  	 * @return	Halbe Breite der Konfidenzintervalle
  	 */
  	public double[] getRunConfidenceHalfWide(final double[] alpha) {
  		if (alpha==null || alpha.length==0) return new double[0];
  		if (sum==0) return new double[alpha.length];
  		if (min==max) return new double[alpha.length];
  
  		final int b=runCount;
  		if (b==0) return new double[alpha.length]; /* Keine Lufe erfasst */
  		if (b==1) return new double[alpha.length]; /* Sorry, aber TDistribution(0) geht auch nicht. */
  
  		final TDistribution dist=new TDistribution(b-1);
  		final double sd=getRunSD();
  
  		final double[] results=new double[alpha.length];
  		for (int i=0;i<alpha.length;i++) {
  			final double t=dist.inverseCumulativeProbability(1-alpha[i]/2);
  			results[i]=t*sd; /* Division durch sqrt(b) steckt schon in getBatchSD() */
  		}
  		return results;
  	}
  
  	/**
  	 * Konfidenzintervall-Levels zum Speichern in der xml-Datei
  	 * @see #addToXMLIntern(Element, StringBuilder)
  	 */
  	private static final double[] CONFIDENCE_SAVE_LEVEL=new double[]{0.1,0.05,0.01};
  
  	@Override
  	protected void addToXMLIntern(final Element node, final StringBuilder recycleStringBuilder) {
  		if (stateTime==null) {
  			final StringBuilder sb;
  			if (recycleStringBuilder==null) {
  				sb=new StringBuilder();
  			} else {
  				sb=recycleStringBuilder;
  				sb.setLength(0);
  			}
  			sb.append(NumberTools.formatSystemNumber(time0>0?time0:0));
  			if (timeMaxState>0) {
  				sb.append(";");
  				for (int i=1;i<Math.min(timeMaxState,maxState);i++) sb.append("0;");
  				sb.append(NumberTools.formatSystemNumber(timeMax));
  			}
  			node.setTextContent(sb.toString());
  		} else {
  			if (getTimeMax()==0 || getTimeMax()==-1) {
  				node.setTextContent(NumberTools.formatSystemNumber(getReadOnlyDistribution().densityData[0]));
  			} else {
  				node.setTextContent(getReadOnlyDistribution().storeToStringShort());
  			}
  		}
  
  		node.setAttribute(xmlNameStart[0],NumberTools.formatSystemNumber(getStartTime(),recycleStringBuilder));
  		node.setAttribute(xmlNameSum,NumberTools.formatSystemNumber(getSum(),recycleStringBuilder));
  		node.setAttribute(xmlNameValues[0],NumberTools.formatSystemNumber(valueSum,recycleStringBuilder));
  		node.setAttribute(xmlNameValuesSquared[0],NumberTools.formatSystemNumber(valueSumSquared,recycleStringBuilder));
  		node.setAttribute(xmlNameValuesCubic[0],NumberTools.formatSystemNumber(valueSumCubic,recycleStringBuilder));
  		node.setAttribute(xmlNameValuesQuartic[0],NumberTools.formatSystemNumber(valueSumQuartic,recycleStringBuilder));
  		node.setAttribute(xmlNameMean,NumberTools.formatSystemNumber(getTimeMean(),recycleStringBuilder));
  		node.setAttribute(xmlNameSD,NumberTools.formatSystemNumber(getTimeSD(),recycleStringBuilder));
  		node.setAttribute(xmlNameCV,NumberTools.formatSystemNumber(getTimeCV(),recycleStringBuilder));
  		node.setAttribute(xmlNameSk[0],NumberTools.formatSystemNumber(getTimeSk(),recycleStringBuilder));
  		node.setAttribute(xmlNameKurt[0],NumberTools.formatSystemNumber(NumberTools.reduceDigits(getTimeKurt(),8),recycleStringBuilder));
  		node.setAttribute(xmlNameMin[0],""+Math.max(0,getTimeMin()));
  		node.setAttribute(xmlNameMax[0],""+Math.max(0,getTimeMax()));
  
  		if (stateTime!=null && getTimeMean()>0) {
  			final int[] quantils=getQuantil(storeQuantilValues);
  			for (int i=0;i<storeQuantilValues.length;i++) {
  				node.setAttribute(xmlNameQuantil+Math.round(storeQuantilValues[i]*100),""+quantils[i]);
  			}
  		}
  
  		if (runCount>1) {
  			node.setAttribute(StatisticsDataPerformanceIndicator.xmlNameRunCount[0],""+runCount);
  			node.setAttribute(StatisticsDataPerformanceIndicator.xmlNameRunVar[0],NumberTools.formatSystemNumber(getRunVar(),recycleStringBuilder));
  			for (double level: CONFIDENCE_SAVE_LEVEL) {
  				String s=String.valueOf(Math.round((1-level)*100));
  				double radius=NumberTools.reduceDigits(getRunConfidenceHalfWide(level),8);
  				node.setAttribute(StatisticsDataPerformanceIndicator.xmlNameRunHalfWide[0]+s,NumberTools.formatSystemNumber(radius,recycleStringBuilder));
  			}
  		}
  	}
  
  	@Override
  	public String loadFromXML(Element node) {
  		final DataDistributionImpl dist=DataDistributionImpl.createFromString(node.getTextContent(),1000);
  		if (dist==null) return String.format(xmlLoadError,node.getNodeName());
  		stateTime=dist.densityData;
  		sum=dist.sum();
  		lastTimeMean=-1;
  
  		String value;
  
  		value=getAttributeValue(node,xmlNameValues);
  		if (!value.isEmpty()) {
  			final Double valueSum=NumberTools.getDouble(value);
  			if (valueSum==null) return String.format(xmlNameValuesError,node.getNodeName(),value);
  			this.valueSum=valueSum;
  		}
  
  		value=getAttributeValue(node,xmlNameValuesSquared);
  		if (!value.isEmpty()) {
  			final Double valueSum2=NumberTools.getDouble(value);
  			if (valueSum2==null || valueSum2<0) return String.format(xmlNameValuesSquaredError,node.getNodeName(),value);
  			valueSumSquared=valueSum2;
  		}
  
  		value=getAttributeValue(node,xmlNameValuesCubic);
  		if (!value.isEmpty()) {
  			final Double valueSum3=NumberTools.getDouble(value);
  			if (valueSum3==null) return String.format(xmlNameValuesCubicError,node.getNodeName(),value);
  			valueSumCubic=valueSum3;
  		}
  
  		value=getAttributeValue(node,xmlNameValuesQuartic);
  		if (!value.isEmpty()) {
  			final Double valueSum4=NumberTools.getDouble(value);
  			if (valueSum4==null) return String.format(xmlNameValuesQuarticError,node.getNodeName(),value);
  			valueSumQuartic=valueSum4;
  		}
  
  		value=getAttributeValue(node,xmlNameStart);
  		if (!value.isEmpty()) {
  			final Double start=NumberTools.getDouble(value);
  			if (start==null) return String.format(xmlNameStartError,node.getNodeName(),value);
  			this.start=start;
  		}
  
  		value=getAttributeValue(node,xmlNameMin);
  		if (!value.isEmpty()) {
  			final Integer min=NumberTools.getInteger(value);
  			if (min==null) return String.format(xmlNameMinError,node.getNodeName(),value);
  			this.min=min;
  		}
  
  		value=getAttributeValue(node,xmlNameMax);
  		if (!value.isEmpty()) {
  			final Integer max=NumberTools.getInteger(value);
  			if (max==null) return String.format(xmlNameMaxError,node.getNodeName(),value);
  			this.max=max;
  		}
  
  		value=getAttributeValue(node,StatisticsDataPerformanceIndicator.xmlNameRunCount);
  		if (!value.isEmpty()) {
  			Long L=NumberTools.getPositiveLong(value);
  			if (L==null) return String.format(StatisticsDataPerformanceIndicator.xmlNameRunCountError,node.getNodeName(),value);
  			runCount=L.intValue();
  		}
  
  		value=getAttributeValue(node,StatisticsDataPerformanceIndicator.xmlNameRunVar);
  		if (!value.isEmpty()) {
  			Double D=NumberTools.getNotNegativeDouble(value);
  			if (D==null) {
  				final Double D2=NumberTools.getDouble(value);
  				if (D2==null || D2.doubleValue()<-0.1) return String.format(StatisticsDataPerformanceIndicator.xmlNameRunVarError,node.getNodeName(),value);
  				runVar=0; /* Frhere Versionen konnten beim Speichern aufgrund von Rundungsungenauigkeiten noch ganz leicht negative Werte speichern. */
  			} else {
  				runVar=D.doubleValue();
  			}
  		}
  
  		return null;
  	}
  }