package de.wwwu.awolf.presenter.algorithms.advanced; import de.wwwu.awolf.model.Interval; import de.wwwu.awolf.model.Line; import de.wwwu.awolf.model.Point; import de.wwwu.awolf.model.communication.AlgorithmData; import de.wwwu.awolf.model.communication.Data; import de.wwwu.awolf.model.communication.SubscriberType; import de.wwwu.awolf.presenter.AbstractPresenter; import de.wwwu.awolf.presenter.algorithms.Algorithm; import de.wwwu.awolf.presenter.util.BinomialCoeffizient; import de.wwwu.awolf.presenter.util.FastElementSelector; import de.wwwu.awolf.presenter.util.IntersectionComputer; import de.wwwu.awolf.presenter.util.RandomSampler; import java.util.*; import java.util.concurrent.Flow; /** * Implementierung verschiedener Algorithmen zur Berechnung von Ausgleichsgeraden. * * @Author: Armin Wolf * @Email: a_wolf28@uni-muenster.de * @Date: 28.05.2017. */ public class TheilSenEstimator implements Algorithm { private static final Algorithm.Type type = Type.TS; private final double POSITIV_INF = 9999.0; private final double NEGATIV_INF = -9999.0; private final double EPSILON = 0.00001; private List setOfLines; //Hilfsvariablen (siehe original Paper) private double j; private int jA; private int jB; private double r; private int n; private double N; private int k; //Intervall und die temporaeren Grenzen private Interval interval; private double aVariant; private double bVariant; private double slope; private double yInterception; private Flow.Subscriber subscriber; private AbstractPresenter presenter; /** * Randomisierter Algorithmus zur Berechnung des Theil-Sen Schätzers. * Algorithmus stammt aus dem Paper: * "Jiri Matousek, Randomized optimal algorithm for slope selection, * Information Processing Letters 39 (1991) 183-187 */ public Line call() { this.n = this.setOfLines.size(); this.N = BinomialCoeffizient.run(n, 2); //this.k = Integer.valueOf((int) (N * 0.5)) - 1; this.k = (int) (N / 2); interval = new Interval(NEGATIV_INF, POSITIV_INF); //damit eine initiale Ordnung herscht //Collections.sort(intervalIntersections); r = n; List intervalIntersections = new LinkedList<>(IntersectionComputer.getInstance().compute(setOfLines, interval.getLower(), interval.getUpper())); while (true) { if (this.N <= n || (Math.abs(interval.getUpper() - interval.getLower())) < EPSILON) { break; } else { //Anzahl der Schnittpunkte im Intervall [-Inf, a) int numberOfIntersections = getIntervalSize(NEGATIV_INF, interval.getLower()); //Randomized Interpolating Search j = (r / N) * (double) (k - numberOfIntersections); jA = (int) Math.max(1, Math.floor(j - (1.5 * Math.sqrt(r)))); jB = (int) Math.min(r, Math.floor(j + (1.5 * Math.sqrt(r)))); /* Suche nach einem passenderen und kleineren Intervall Schleife terminiert wenn die das k-te Elemnet zwischen aVariant und bVariant liegt und das Intrvall weniger als 11*N / sqrt(r) Elemente besitzt */ do { //zufällige Stichprobe List sampledIntersections = RandomSampler.run(intervalIntersections, r); aVariant = FastElementSelector.randomizedSelect(getIntersectionAbscissas(sampledIntersections), jA); bVariant = FastElementSelector.randomizedSelect(getIntersectionAbscissas(sampledIntersections), jB); } while (!checkCondition()); interval.setLower(aVariant); interval.setUpper(bVariant); intervalIntersections = getOpenIntervalElements(interval.getLower(), interval.getUpper()); N = getIntervalSize(interval.getLower(), interval.getUpper()); } } return pepareResult(); } @Override public void setInput(Set lines) { this.setOfLines = new LinkedList<>(lines); } @Override public Type getType() { return type; } @Override public void setPresenter(AbstractPresenter presenter) { this.presenter = presenter; subscribe(presenter); } private List getIntersectionAbscissas(List interections) { List abscissas = new ArrayList<>(); interections.forEach(e -> abscissas.add(e.getX())); return abscissas; } /** * Diese Funktion überprüft ob die Bedingung für das Interval erfüllt ist. Dabei muss der k-te * Schnittpunkt in diesem Interval enthalten sein. des weiteren soll die Anzahl der Schnittpunkte * im Interval kleiner oder gleich dem Term: (11*N)/sqrt(r) sein. * * @return Boolscher Wert ob die Bedingung erfüllt ist */ private Boolean checkCondition() { //Double kthElement = FastElementSelector.randomizedSelect(xCoordinates, k); //Boolean cond1 = (kthElement > aVariant) && (kthElement <= bVariant); int lowerCount = getIntervalSize(NEGATIV_INF, aVariant); int higherCount = getIntervalSize(NEGATIV_INF, bVariant); Boolean conda = k > lowerCount; Boolean condb = k <= higherCount; Boolean cond1 = conda && condb; Boolean cond2 = (higherCount - lowerCount) <= ((11 * N) / Math.sqrt(r)); return (cond1 && cond2) || (aVariant == bVariant); } /** * Berechne wieviele von den Schnittpunkten in dem Interval zwischen a und b * enthalten sind. * * @param a untere Grenze des Intervals * @param b obere Grenze des Intrvals * @return Anzahl der Schnittpunkte im Interval [a,b) */ public int getIntervalSize(double a, double b) { return getOpenIntervalElements(a, b).size(); } /** * Berechne wieviele von den Schnittpunkten in dem Interval zwischen a und b * enthalten sind. Zusätzlich werden diese Schnittpunkte in einer Liste festgehalten und diese werden * zurückgeliefert. * * @param a untere Grenze des Intervals * @param b obere Grenze des Intrvals * @return Liste der Schnittpunkte die im Interval (a,b) vertreten sind */ public List getOpenIntervalElements(double a, double b) { Collection intersections = IntersectionComputer.getInstance().compute(setOfLines, a, b); return new ArrayList<>(intersections); } private Line pepareResult() { double m, x; double b, y; List resultSt = getOpenIntervalElements(interval.getLower(), interval.getUpper()); List resultAbscissas = new ArrayList<>(); for (Point p : resultSt) { resultAbscissas.add(p.getX()); } List yCoords = new ArrayList<>(); for (Point p : getOpenIntervalElements(interval.getLower(), interval.getUpper())) { yCoords.add(p.getY()); } double pseudoIndex = getIntervalSize(NEGATIV_INF, interval.getLower()) * 1.0; m = FastElementSelector.randomizedSelect(resultAbscissas, k - pseudoIndex); Set unique = new LinkedHashSet<>(yCoords); yCoords.clear(); yCoords.addAll(unique); b = FastElementSelector.randomizedSelect(yCoords, yCoords.size() * 0.5) * -1; slope = m; yInterception = b; if (this.subscriber != null) { AlgorithmData data = new AlgorithmData(); data.setAlgorithmType(getType()); data.setType(SubscriberType.ALGORITHM); data.setLineData(new Line(m, b)); this.subscriber.onNext(data); } return new Line(getSlope(), getYInterception()); } /** * @return Steigung */ public Double getSlope() { return slope; } /** * @return y-Achsenabschnitt */ public Double getYInterception() { return yInterception; } @Override public void subscribe(Flow.Subscriber subscriber) { this.subscriber = subscriber; } }