content: /**
   * Copyright 2021 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 org.w3c.dom.Element;
  
  import mathtools.NumberTools;
  
  /**
   * Statistik-Klasse, die erfasst, wie lange sich das System in einem bestimmten, durch einen
   * Double-Zahlenwert benannten Zustand befunden hat.<br>
   * Die Zhlung wird ber die Funktion {@link StatisticsTimeContinuousPerformanceIndicator#set(double, double)} realisiert.<br>
   * @author Alexander Herzog
   * @version 1.1
   */
  public final class StatisticsTimeContinuousPerformanceIndicator extends StatisticsPerformanceIndicator implements Cloneable {
  	/**
  	 * Zeitpunkt der letzten nderung des Zustands des Systems.
  	 */
  	private double lastTime=0.0;
  
  	/**
  	 * Aktueller Zustand des Systems.
  	 */
  	private double lastState=0;
  
  	/**
  	 * Summe der erfassten Zeiten
  	 */
  	private double sum=0;
  
  	/**
  	 * Minimaler Zustand in dem sich das System eine Zeitdauer >0 befunden hat
  	 */
  	private double min=0;
  
  	/**
  	 * Maximaler Zustand in dem sich das System eine Zeitdauer >0 befunden hat
  	 */
  	private double max=0;
  
  	/**
  	 * 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 StatisticsTimeContinuousPerformanceIndicator#setTime
  	 */
  	private boolean explicitTimeInit=false;
  
  	/**
  	 * Konstruktor der Klasse <code>StatisticsTimePerformanceIndicator</code>
  	 * @param xmlNodeNames	Name des xml-Knotens, in dem die Daten gespeichert werden sollen
  	 */
  	public StatisticsTimeContinuousPerformanceIndicator(final String[] xmlNodeNames) {
  		super(xmlNodeNames);
  		reset();
  	}
  
  	/**
  	 * Erfasst eine Zustandsnderung
  	 * @param time	Aktuelle Zeit
  	 * @param newState	Neuer Zustand (muss eine nicht-negative Zahl sein)
  	 */
  	public void set(final double time, final double newState) {
  		if (time!=lastTime) {
  			final boolean init=(lastTime<=0) || explicitTimeInit;
  			explicitTimeInit=false;
  
  			if (init) {
  				this.min=0;
  				this.max=0;
  				sum=0;
  				valueSum=0;
  				valueSumSquared=0;
  				valueSumCubic=0;
  				valueSumQuartic=0;
  			} else {
  				final double add=time-lastTime;
  
  				if (lastState<this.min || this.sum==0) this.min=lastState;
  				if (lastState>this.max || this.sum==0) this.max=lastState;
  				sum+=add;
  				valueSum+=add*lastState;
  				valueSumSquared+=add*lastState*lastState;
  				valueSumCubic+=add*lastState*lastState*lastState;
  				valueSumQuartic+=add*lastState*lastState*lastState*lastState;
  			}
  
  			lastTime=time;
  		}
  
  		lastState=newState;
  		lastTimeMean=-1;
  	}
  
  	/**
  	 * Liefert den zuletzt per <code>set()</code> eingestellten Zustand
  	 * @return	Aktuell eingestellter Zustand
  	 * @see #set(double, double)
  	 */
  	public double 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;
  	}
  
  	@Override
  	public void add(StatisticsPerformanceIndicator moreStatistics) {
  		if (!(moreStatistics instanceof StatisticsTimeContinuousPerformanceIndicator)) return;
  		StatisticsTimeContinuousPerformanceIndicator moreCountStatistics=(StatisticsTimeContinuousPerformanceIndicator)moreStatistics;
  
  		if (moreCountStatistics.sum>0) {
  			if (sum>0) {
  				min=Math.min(min,moreCountStatistics.min);
  				max=Math.max(max,moreCountStatistics.max);
  			} else {
  				min=moreCountStatistics.min;
  				max=moreCountStatistics.max;
  			}
  		}
  
  		sum+=moreCountStatistics.sum;
  		valueSum+=moreCountStatistics.valueSum;
  		valueSumSquared+=moreCountStatistics.valueSumSquared;
  		valueSumCubic+=moreCountStatistics.valueSumCubic;
  		valueSumQuartic+=moreCountStatistics.valueSumQuartic;
  
  		lastTimeMean=-1;
  	}
  
  	/**
  	 * Setzt alle Teil-Kenngren auf 0 zurck.
  	 */
  	@Override
  	public void reset() {
  		lastState=0;
  		lastTime=0;
  
  		min=0;
  		max=0;
  		sum=0;
  		valueSum=0;
  		valueSumSquared=0;
  		valueSumCubic=0;
  		valueSumQuartic=0;
  
  		lastTimeMean=-1;
  	}
  
  	/**
  	 * 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 StatisticsTimeContinuousPerformanceIndicator)) return;
  		final StatisticsTimeContinuousPerformanceIndicator time=(StatisticsTimeContinuousPerformanceIndicator)indicator;
  
  		lastState=time.lastState;
  		lastTime=time.lastTime;
  
  		min=time.min;
  		max=time.max;
  		sum=time.sum;
  		valueSum=time.valueSum;
  		valueSumSquared=time.valueSumSquared;
  		valueSumCubic=time.valueSumCubic;
  		valueSumQuartic=time.valueSumQuartic;
  
  		lastTimeMean=-1;
  	}
  
  	/**
  	 * Legt eine Kopie des Objekts an.
  	 * @return Kopie des Statistik-Objektes
  	 */
  	@Override
  	public StatisticsTimeContinuousPerformanceIndicator clone() {
  		final StatisticsTimeContinuousPerformanceIndicator indicator=new StatisticsTimeContinuousPerformanceIndicator(xmlNodeNames);
  		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 StatisticsTimeContinuousPerformanceIndicator cloneEmpty() {
  		return new StatisticsTimeContinuousPerformanceIndicator(xmlNodeNames);
  	}
  
  	/**
  	 * Liefert die Summe der Zeiten der Systemzustnde zurck.
  	 * @return	Summe der Zeiten der Systemzustnde
  	 */
  	public double getSum() {
  		return sum;
  	}
  
  	/**
  	 * 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;
  		}
  		return lastTimeMean;
  	}
  
  	/**
  	 * 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() {
  		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() {
  		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() {
  		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 double getTimeMax() {
  		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 double getTimeMin() {
  		return min;
  	}
  
  	@Override
  	protected void addToXMLIntern(final Element node, final StringBuilder recycleStringBuilder) {
  		node.setAttribute(StatisticsTimePerformanceIndicator.xmlNameSum,NumberTools.formatSystemNumber(getSum(),recycleStringBuilder));
  		node.setAttribute(StatisticsTimePerformanceIndicator.xmlNameValues[0],NumberTools.formatSystemNumber(valueSum,recycleStringBuilder));
  		node.setAttribute(StatisticsTimePerformanceIndicator.xmlNameValuesSquared[0],NumberTools.formatSystemNumber(valueSumSquared,recycleStringBuilder));
  		node.setAttribute(StatisticsTimePerformanceIndicator.xmlNameValuesCubic[0],NumberTools.formatSystemNumber(valueSumCubic,recycleStringBuilder));
  		node.setAttribute(StatisticsTimePerformanceIndicator.xmlNameValuesQuartic[0],NumberTools.formatSystemNumber(valueSumQuartic,recycleStringBuilder));
  		node.setAttribute(StatisticsTimePerformanceIndicator.xmlNameMean,NumberTools.formatSystemNumber(getTimeMean(),recycleStringBuilder));
  		node.setAttribute(StatisticsTimePerformanceIndicator.xmlNameSD,NumberTools.formatSystemNumber(getTimeSD(),recycleStringBuilder));
  		node.setAttribute(StatisticsTimePerformanceIndicator.xmlNameCV,NumberTools.formatSystemNumber(getTimeCV(),recycleStringBuilder));
  		node.setAttribute(StatisticsTimePerformanceIndicator.xmlNameSk[0],NumberTools.formatSystemNumber(getTimeSk(),recycleStringBuilder));
  		node.setAttribute(StatisticsTimePerformanceIndicator.xmlNameKurt[0],NumberTools.formatSystemNumber(getTimeKurt(),recycleStringBuilder));
  		node.setAttribute(StatisticsTimePerformanceIndicator.xmlNameMin[0],""+Math.max(0,getTimeMin()));
  		node.setAttribute(StatisticsTimePerformanceIndicator.xmlNameMax[0],""+Math.max(0,getTimeMax()));
  	}
  
  	@Override
  	public String loadFromXML(Element node) {
  		lastTimeMean=-1;
  
  		String value;
  
  		value=getAttributeValue(node,new String[] {StatisticsTimePerformanceIndicator.xmlNameSum});
  		if (!value.isEmpty()) {
  			final Double sum=NumberTools.getDouble(value);
  			if (sum==null) return String.format(StatisticsTimePerformanceIndicator.xmlNameValuesError,node.getNodeName(),value);
  			this.sum=sum;
  		}
  
  		value=getAttributeValue(node,StatisticsTimePerformanceIndicator.xmlNameValues);
  		if (!value.isEmpty()) {
  			final Double valueSum=NumberTools.getDouble(value);
  			if (valueSum==null) return String.format(StatisticsTimePerformanceIndicator.xmlNameValuesError,node.getNodeName(),value);
  			this.valueSum=valueSum;
  		}
  
  		value=getAttributeValue(node,StatisticsTimePerformanceIndicator.xmlNameValuesSquared);
  		if (!value.isEmpty()) {
  			final Double valueSum2=NumberTools.getDouble(value);
  			if (valueSum2==null || valueSum2<0) return String.format(StatisticsTimePerformanceIndicator.xmlNameValuesSquaredError,node.getNodeName(),value);
  			valueSumSquared=valueSum2;
  		}
  
  		value=getAttributeValue(node,StatisticsTimePerformanceIndicator.xmlNameValuesCubic);
  		if (!value.isEmpty()) {
  			final Double valueSum3=NumberTools.getDouble(value);
  			if (valueSum3==null) return String.format(StatisticsTimePerformanceIndicator.xmlNameValuesCubicError,node.getNodeName(),value);
  			valueSumCubic=valueSum3;
  		}
  
  		value=getAttributeValue(node,StatisticsTimePerformanceIndicator.xmlNameValuesQuartic);
  		if (!value.isEmpty()) {
  			final Double valueSum4=NumberTools.getDouble(value);
  			if (valueSum4==null) return String.format(StatisticsTimePerformanceIndicator.xmlNameValuesQuarticError,node.getNodeName(),value);
  			valueSumCubic=valueSum4;
  		}
  
  		value=getAttributeValue(node,StatisticsTimePerformanceIndicator.xmlNameMin);
  		if (!value.isEmpty()) {
  			final Double min=NumberTools.getDouble(value);
  			if (min==null) return String.format(StatisticsTimePerformanceIndicator.xmlNameMinError,node.getNodeName(),value);
  			this.min=min;
  		}
  
  		value=getAttributeValue(node,StatisticsTimePerformanceIndicator.xmlNameMax);
  		if (!value.isEmpty()) {
  			final Double max=NumberTools.getDouble(value);
  			if (max==null) return String.format(StatisticsTimePerformanceIndicator.xmlNameMaxError,node.getNodeName(),value);
  			this.max=max;
  		}
  
  		return null;
  	}
  }