diff --git a/measure/pom.xml b/measure/pom.xml
index 7351c53..dff0b6e 100644
--- a/measure/pom.xml
+++ b/measure/pom.xml
@@ -18,6 +18,11 @@
power-server-metadata
${project.version}
+
+ org.apache.commons
+ commons-math3
+ 3.6.1
+
diff --git a/measure/src/main/java/net/laprun/sustainability/power/measure/PowerMeasure.java b/measure/src/main/java/net/laprun/sustainability/power/measure/PowerMeasure.java
index 6c9fcfb..825fc57 100644
--- a/measure/src/main/java/net/laprun/sustainability/power/measure/PowerMeasure.java
+++ b/measure/src/main/java/net/laprun/sustainability/power/measure/PowerMeasure.java
@@ -1,31 +1,14 @@
package net.laprun.sustainability.power.measure;
import java.time.Duration;
-import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
+import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
+
import net.laprun.sustainability.power.SensorMetadata;
public interface PowerMeasure {
- int numberOfSamples();
-
- Duration duration();
-
- default double average() {
- return total() / numberOfSamples();
- }
-
- double total();
-
- SensorMetadata metadata();
-
- double[] averagesPerComponent();
-
- double minMeasuredTotal();
-
- double maxMeasuredTotal();
-
private static double sumOfComponents(double[] recorded) {
var componentSum = 0.0;
for (double value : recorded) {
@@ -45,35 +28,65 @@ static double sumOfSelectedComponents(double[] recorded, int... indices) {
return componentSum;
}
+ static String asString(PowerMeasure measure) {
+ final var durationInSeconds = measure.duration().getSeconds();
+ final var samples = measure.numberOfSamples();
+ final var measuredMilliWatts = measure.total();
+ final var stdDevs = measure.standardDeviations();
+ return String.format("%s / avg: %s / std dev: %.3f [min: %.3f, max: %.3f] (%ds, %s samples)",
+ readableWithUnit(measuredMilliWatts), readableWithUnit(measure.average()), stdDevs.aggregate,
+ measure.minMeasuredTotal(), measure.maxMeasuredTotal(), durationInSeconds, samples);
+ }
+
+ static String readableWithUnit(double milliWatts) {
+ String unit = milliWatts >= 1000 ? "W" : "mW";
+ double power = milliWatts >= 1000 ? milliWatts / 1000 : milliWatts;
+ return String.format("%.3f%s", power, unit);
+ }
+
+ int numberOfSamples();
+
+ Duration duration();
+
+ default double average() {
+ return total() / numberOfSamples();
+ }
+
+ double total();
+
+ SensorMetadata metadata();
+
+ double[] averagesPerComponent();
+
+ double minMeasuredTotal();
+
+ double maxMeasuredTotal();
+
default StdDev standardDeviations() {
final var cardinality = metadata().componentCardinality();
final var totalComponents = metadata().totalComponents();
- final var stdDevs = new double[cardinality];
- final var aggregate = new double[1];
- final var samples = numberOfSamples() - 1; // unbiased so we remove one sample
- final var sqrdAverages = Arrays.stream(averagesPerComponent()).map(m -> m * m).toArray();
- final var sqrdAverage = average() * average();
- // need to compute the average of variances then square root that to get the "aggregate" standard deviation,
- // see: https://stats.stackexchange.com/a/26647
- // "vectorize" computation of variances: compute the variance for each component in parallel
+ final DescriptiveStatistics[] perComponent = new DescriptiveStatistics[cardinality];
+ for (int i = 0; i < perComponent.length; i++) {
+ perComponent[i] = new DescriptiveStatistics();
+ }
+ final DescriptiveStatistics total = new DescriptiveStatistics();
IntStream.range(0, cardinality).parallel()
- // compute variances for each component of the measure
.forEach(component -> {
- final var sumOfSquares = measures().stream().parallel().peek(m -> {
- // compute the std dev for total measure
- final var total = sumOfSelectedComponents(m, totalComponents);
- aggregate[0] += total * total;
- }).mapToDouble(m -> m[component] * m[component]).sum();
- stdDevs[component] = stdDev(sumOfSquares, sqrdAverages[component], samples);
- aggregate[0] = stdDev(aggregate[0], sqrdAverage, samples);
+ measures().stream().parallel().forEach(measure -> {
+ perComponent[component].addValue(measure[component]);
+ total.addValue(sumOfSelectedComponents(measure, totalComponents));
+ });
});
- return new StdDev(aggregate[0], stdDevs);
- }
- private static double stdDev(double sumOfSquares, double squaredAvg, int samples) {
- return Math.sqrt((sumOfSquares / samples) - (((samples + 1) * squaredAvg) / samples));
+ final var stdDevs = new double[cardinality];
+ for (int i = 0; i < perComponent.length; i++) {
+ stdDevs[i] = perComponent[i].getStandardDeviation();
+ }
+ return new StdDev(total.getStandardDeviation(), stdDevs);
}
+ List measures();
+
/**
* Records the standard deviations for the aggregated energy comsumption value (as returned by {@link #total()}) and
* per component
@@ -83,22 +96,4 @@ private static double stdDev(double sumOfSquares, double squaredAvg, int samples
*/
record StdDev(double aggregate, double[] perComponent) {
}
-
- static String asString(PowerMeasure measure) {
- final var durationInSeconds = measure.duration().getSeconds();
- final var samples = measure.numberOfSamples();
- final var measuredMilliWatts = measure.total();
- final var stdDevs = measure.standardDeviations();
- return String.format("%s / avg: %s / std dev: %.3f [min: %.3f, max: %.3f] (%ds, %s samples)",
- readableWithUnit(measuredMilliWatts), readableWithUnit(measure.average()), stdDevs.aggregate,
- measure.minMeasuredTotal(), measure.maxMeasuredTotal(), durationInSeconds, samples);
- }
-
- static String readableWithUnit(double milliWatts) {
- String unit = milliWatts >= 1000 ? "W" : "mW";
- double power = milliWatts >= 1000 ? milliWatts / 1000 : milliWatts;
- return String.format("%.3f%s", power, unit);
- }
-
- List measures();
}
diff --git a/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java b/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java
index 9ab8fe6..f9a4d8a 100644
--- a/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java
+++ b/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java
@@ -43,8 +43,8 @@ public int componentCardinality() {
assertEquals((m1c1 + m2c1) / 2, c1Avg);
assertEquals((m1c2 + m2c2) / 2, c2Avg);
- final var stdVarForC1 = Math.sqrt(Math.pow(m1c1 - c1Avg, 2) + Math.pow(m2c1 - c1Avg, 2));
- final var stdVarForC2 = Math.sqrt(Math.pow(m1c2 - c2Avg, 2) + Math.pow(m2c2 - c2Avg, 2));
+ final var stdVarForC1 = Math.sqrt((Math.pow(m1c1 - c1Avg, 2) + Math.pow(m2c1 - c1Avg, 2)) / (2 - 1));
+ final var stdVarForC2 = Math.sqrt((Math.pow(m1c2 - c2Avg, 2) + Math.pow(m2c2 - c2Avg, 2)) / (2 - 1));
assertEquals(stdVarForC1, measure.standardDeviations().perComponent()[0], 0.0001,
"Standard Deviation did not match the expected value");