com.mgmtp.perfload.loadprofiles.ui.util.GraphPointsCalculator Maven / Gradle / Ivy
/*
* Copyright (c) 2013 mgm technology partners GmbH
*
* 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 com.mgmtp.perfload.loadprofiles.ui.util;
import static com.google.common.collect.Sets.newLinkedHashSet;
import static com.google.common.collect.Sets.newTreeSet;
import static java.lang.Math.ulp;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.mgmtp.perfload.loadprofiles.model.CurveAssignment;
import com.mgmtp.perfload.loadprofiles.ui.model.LoadProfileEntity;
import com.mgmtp.perfload.loadprofiles.ui.model.Stairs;
/**
* Calculates points for the load curve graph.
*
* @author rnaegele
*/
public class GraphPointsCalculator {
/**
* Calculates graph points for the given list of curve assignments (one graph per curve name).
*
* @param stairs
* The list of curve assignments
* @return A map of lists of points. The curve names are the keys.
*/
public Map> calculatePoints(final Collection stairs) {
List curveNames = computeCurveNames(stairs);
Map> pointsMap = Maps.newLinkedHashMap();
for (final String curveName : curveNames) {
List curves = Lists.newArrayList();
for (Stairs item : stairs) {
if (item.operation.getName().equals(curveName)) {
CurveItem curve = new CurveItem(calculatePoints(item));
curves.add(curve);
}
}
pointsMap.put(curveName, sumUpCurves(curves));
}
return pointsMap;
}
/**
* Sums up the specified curves returning a list of points representing one curve.
*
* @param curves
* A list of separate curves
* @return The summed list of points
*/
private Set sumUpCurves(final List curves) {
Map curvePoints = Maps.newHashMap();
Set xValues = newLinkedHashSet();
// Determine all x-values that have points on the separate curves
// and collect all points by their x-values
int curveCount = curves.size();
for (int i = 0; i < curveCount; ++i) {
CurveItem curve = curves.get(i);
for (Point p : curve.getPoints()) {
Double x = p.getX();
Point[] pointsForX = curvePoints.get(x);
if (pointsForX == null) {
pointsForX = new Point[curveCount];
curvePoints.put(x, pointsForX);
}
// Store point in the array for the x-value. Array elements may remain emtpy (null),
// if there is no point for an x-value for the curve with the current index.
pointsForX[i] = p;
xValues.add(x);
}
}
Set points = newLinkedHashSet();
// iterate over x-values
for (Double x : xValues) {
// if (i == 0) {
// // y is zero for the first point
// points.add(new Point(x, 0.));
// } else {
// Get points for a certain x-value
Point[] pointsForX = curvePoints.get(x);
double sumY = 0.;
for (int j = 0, len = pointsForX.length; j < len; ++j) {
Point p = pointsForX[j];
if (p == null) {
CurveItem curve = curves.get(j);
if (curve.isInRange(x)) {
// If the curve is in range but does not have a direct y-value for this x,
// we need to calculate the value on the line between the previous and the next point of the curve.
double y = curve.calculateYForX(x);
// Add calculated y-value
sumY += y;
}
} else {
// If there is a y-value for this x, we can directly add it.
sumY += p.getY();
}
// }
}
// Create a new point with current x-value and the sum of y-values
points.add(new Point(x, sumY));
}
return points;
}
/**
* Calculates graph points for the given curve assignment.
*
* @param stairs
* The load profile entity
* @return A list of points
*/
public Set calculatePoints(final Stairs stairs) {
Set points = newLinkedHashSet();
int numSteps = stairs.numSteps;
int offset = stairs.a + stairs.b;
points.add(Point.of(stairs.t0, 0));
for (int i = 0; i < numSteps; ++i) {
points.add(Point.of(stairs.t0 + stairs.a + i * offset, (i + 1) * stairs.h));
points.add(Point.of(stairs.t0 + stairs.a + stairs.b + i * offset, (i + 1) * stairs.h));
}
points.add(Point.of(stairs.t0 + numSteps * offset + stairs.c, 0));
// A single curve must have distinct x-values, which is not the case, if a or c are set to 0. Otherwise the logic
// for summing up single curves would not work. Thus, we simply move the first x-value of two distinct
// ones by an ulp to the left.
Point previous = null;
Set changedPoints = newLinkedHashSet();
for (Point p : points) {
if (previous != null) {
if (previous.getX() == p.getX()) {
changedPoints.add(Point.of(previous.getX() - ulp(previous.getX()), previous.getY()));
} else {
changedPoints.add(previous);
}
}
previous = p;
}
changedPoints.add(previous);
return changedPoints;
}
private List computeCurveNames(final Collection loadProfileEnities) {
List curveNames = Lists.newArrayList();
for (LoadProfileEntity lpe : loadProfileEnities) {
if (lpe instanceof CurveAssignment) {
CurveAssignment ca = (CurveAssignment) lpe;
if (!curveNames.contains(ca.operation.getName())) {
curveNames.add(ca.operation.getName());
}
}
}
Collections.sort(curveNames);
return curveNames;
}
private static class CurveItem {
private final NavigableSet points;
private final double minX;
private final double maxX;
/**
* Creates a curve from the specified points
*
* @param points
* the points that make up the curve
*/
CurveItem(final Set points) {
this.points = newTreeSet(points);
this.minX = this.points.iterator().next().getX();
this.maxX = this.points.descendingIterator().next().getX();
}
boolean isInRange(final double x) {
return x >= minX && x <= maxX;
}
/**
* Calculates the y-value on this curve for the given x.
*/
double calculateYForX(final double x) {
Point p1 = null;
for (Point p : points) {
if (p.getX() == x) {
return p.getY();
}
if (p1 != null) {
Point p2 = p;
if (p1.getX() < x && p2.getX() > x) {
return calculateYOnLine(p1, p2, x);
}
}
p1 = p;
}
throw new IllegalArgumentException(x + " not in range of curve");
}
/**
* Calculates the y-value for x on a line through points p1 and p2.
*/
double calculateYOnLine(final Point p1, final Point p2, final double x) {
// slope of a line:
// m = (y2 - y1) / (x2 - x1)
double m = (p2.getY() - p1.getY()) / (p2.getX() - p1.getX());
// y-intercept of the line
// c = y - mx
double c = p1.getY() - m * p1.getX();
// value for y on the line between p1 and p2
double y = m * x + c;
return y;
}
public NavigableSet getPoints() {
return newTreeSet(points);
}
}
}