Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,93 +1,46 @@
package net.laprun.sustainability.power.analysis.total;

import java.util.Arrays;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

import net.laprun.sustainability.power.Errors;
import net.laprun.sustainability.power.SensorMetadata;
import net.laprun.sustainability.power.SensorUnit;
import net.laprun.sustainability.power.analysis.MeasureProcessor;

public class TotalMeasureProcessor implements MeasureProcessor {
private final String name;
private final Totaler totaler;
private double minTotal = Double.MAX_VALUE;
private double maxTotal;
private double accumulatedTotal;
private final Function<double[], Double> formula;
private final SensorUnit expectedResultUnit;

public TotalMeasureProcessor(SensorMetadata metadata, SensorUnit expectedResultUnit, int... totalComponentIndices) {
Objects.requireNonNull(totalComponentIndices, "Must specify component indices that will aggregated in a total");
this.expectedResultUnit = Objects.requireNonNull(expectedResultUnit, "Must specify expected result unit");

final var errors = new Errors();
final var totalComponents = Arrays.stream(totalComponentIndices)
.mapToObj(i -> toTotalComponent(metadata, i, errors))
.toArray(TotalComponent[]::new);
name = Arrays.stream(totalComponents)
.map(TotalComponent::name)
.collect(Collectors.joining(" + ", "Aggregated total from (", ")"));
formula = components -> {
double result = 0;
for (var totalComponent : totalComponents) {
result += components[totalComponent.index] * totalComponent.factor;
}
return result;
};

if (errors.hasErrors()) {
throw new IllegalArgumentException(errors.formatErrors());
}
}

private TotalComponent toTotalComponent(SensorMetadata metadata, int index, Errors errors) {
final var cm = metadata.metadataFor(index);
final var name = cm.name();
final var unit = cm.unit();
if (!unit.isCommensurableWith(expectedResultUnit)) {
errors.addError("Component " + name
+ " is not commensurable with the expected base unit: " + expectedResultUnit);
}

final var factor = unit.factor();
return new TotalComponent(name, index, factor);
this.totaler = new Totaler(metadata, expectedResultUnit, totalComponentIndices);
totaler.validate();
}

public double total() {
return convertToExpectedUnit(accumulatedTotal);
return accumulatedTotal;
}

public double minMeasuredTotal() {
return minTotal == Double.MAX_VALUE ? 0.0 : convertToExpectedUnit(minTotal);
return minTotal == Double.MAX_VALUE ? 0.0 : minTotal;
}

public double maxMeasuredTotal() {
return convertToExpectedUnit(maxTotal);
}

private double convertToExpectedUnit(double value) {
return value * expectedResultUnit.base().conversionFactorTo(expectedResultUnit);
}

private record TotalComponent(String name, int index, double factor) {
return maxTotal;
}

@Override
public String name() {
return name;
return totaler.name();
}

@Override
public String output() {
final var symbol = expectedResultUnit.symbol();
final var symbol = totaler.expectedResultUnit().symbol();
return String.format("%.2f%s (min: %.2f / max: %.2f)", total(), symbol, minMeasuredTotal(), maxMeasuredTotal());
}

@Override
public void recordMeasure(double[] measure, long timestamp) {
final double recordedTotal = formula.apply(measure);
final double recordedTotal = totaler.computeTotalFrom(measure);
accumulatedTotal += recordedTotal;
if (recordedTotal < minTotal) {
minTotal = recordedTotal;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,74 +1,26 @@
package net.laprun.sustainability.power.analysis.total;

import java.util.Arrays;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

import net.laprun.sustainability.power.Errors;
import net.laprun.sustainability.power.SensorMetadata;
import net.laprun.sustainability.power.SensorUnit;
import net.laprun.sustainability.power.analysis.SyntheticComponent;

public class TotalSyntheticComponent implements SyntheticComponent {
private final Function<double[], Double> formula;
private final SensorUnit expectedResultUnit;
private final Totaler totaler;
private final SensorMetadata.ComponentMetadata metadata;

public TotalSyntheticComponent(SensorMetadata metadata, SensorUnit expectedResultUnit, int... totalComponentIndices) {
Objects.requireNonNull(totalComponentIndices, "Must specify component indices that will aggregated in a total");
this.expectedResultUnit = Objects.requireNonNull(expectedResultUnit, "Must specify expected result unit");

final var errors = new Errors();
final var totalComponents = Arrays.stream(totalComponentIndices)
.mapToObj(i -> toTotalComponent(metadata, i, errors))
.toArray(TotalComponent[]::new);
final String description = Arrays.stream(totalComponents)
.map(TotalComponent::name)
.collect(Collectors.joining(" + ", "Aggregated total from (", ")"));
final String name = Arrays.stream(totalComponents)
.map(TotalComponent::name)
.collect(Collectors.joining("_", "total", ""));
this.totaler = new Totaler(metadata, expectedResultUnit, totalComponentIndices);
final var isAttributed = metadata.components().values().stream()
.map(SensorMetadata.ComponentMetadata::isAttributed)
.reduce(Boolean::logicalAnd).orElse(false);
formula = components -> {
double result = 0;
for (var totalComponent : totalComponents) {
result += components[totalComponent.index] * totalComponent.factor;
}
return result;
};

final var name = totaler.name();
if (metadata.exists(name)) {
errors.addError("Component " + name + " already exists");
totaler.addError("Component " + name + " already exists");
}

if (errors.hasErrors()) {
throw new IllegalArgumentException(errors.formatErrors());
}

this.metadata = new SensorMetadata.ComponentMetadata(name, description, isAttributed, expectedResultUnit);
}

private double convertToExpectedUnit(double value) {
return value * expectedResultUnit.base().conversionFactorTo(expectedResultUnit);
}

private record TotalComponent(String name, int index, double factor) {
}

private TotalComponent toTotalComponent(SensorMetadata metadata, int index, Errors errors) {
final var cm = metadata.metadataFor(index);
final var name = cm.name();
final var unit = cm.unit();
if (!unit.isCommensurableWith(expectedResultUnit)) {
errors.addError("Component " + name
+ " is not commensurable with the expected base unit: " + expectedResultUnit);
}
totaler.validate();

final var factor = unit.factor();
return new TotalComponent(name, index, factor);
this.metadata = new SensorMetadata.ComponentMetadata(name, "Aggregated " + name, isAttributed, expectedResultUnit);
}

@Override
Expand All @@ -78,6 +30,6 @@ public SensorMetadata.ComponentMetadata metadata() {

@Override
public double synthesizeFrom(double[] components, long timestamp) {
return convertToExpectedUnit(formula.apply(components));
return totaler.computeTotalFrom(components);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package net.laprun.sustainability.power.analysis.total;

import java.util.Arrays;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

import net.laprun.sustainability.power.Errors;
import net.laprun.sustainability.power.SensorMetadata;
import net.laprun.sustainability.power.SensorUnit;

class Totaler {
private final SensorUnit expectedResultUnit;
private final Function<double[], Double> formula;
private final String name;
private Errors errors;

Totaler(SensorMetadata metadata, SensorUnit expectedResultUnit, int... totalComponentIndices) {
Objects.requireNonNull(totalComponentIndices, "Must specify component indices that will aggregated in a total");
this.expectedResultUnit = Objects.requireNonNull(expectedResultUnit, "Must specify expected result unit");

errors = new Errors();
final var totalComponents = Arrays.stream(totalComponentIndices)
.mapToObj(i -> from(metadata, i, expectedResultUnit, errors))
.toArray(TotalComponent[]::new);
name = Arrays.stream(totalComponents)
.map(TotalComponent::name)
.collect(Collectors.joining(" + ", "total (", ")"));
formula = formulaFrom(totalComponents);
}

void validate() {
if (errors.hasErrors()) {
throw new IllegalArgumentException(errors.formatErrors());
}
errors = null;
}

public void addError(String message) {
if (errors == null) {
throw new IllegalStateException("Totaler has already been validated!");
}
errors.addError(message);
}

public String name() {
return name;
}

public SensorUnit expectedResultUnit() {
return expectedResultUnit;
}

public double computeTotalFrom(double[] measure) {
checkValidated();
return convertToExpectedUnit(formula.apply(measure));
}

private void checkValidated() {
if (errors != null) {
throw new IllegalStateException("Totaler must be validated before use!");
}
}

private double convertToExpectedUnit(double value) {
return value * expectedResultUnit.base().conversionFactorTo(expectedResultUnit);
}

private record TotalComponent(String name, int index, double factor) {
double scaledValueFrom(double[] componentValues) {
return componentValues[index] * factor;
}
}

private static TotalComponent from(SensorMetadata metadata, int index, SensorUnit expectedResultUnit, Errors errors) {
final var cm = metadata.metadataFor(index);
final var name = cm.name();
final var unit = cm.unit();
if (!unit.isCommensurableWith(expectedResultUnit)) {
errors.addError("Component " + name
+ " is not commensurable with the expected base unit: " + expectedResultUnit);
}

final var factor = unit.factor();
return new TotalComponent(name, index, factor);
}

private static Function<double[], Double> formulaFrom(TotalComponent[] totalComponents) {
return components -> {
double result = 0;
for (var totalComponent : totalComponents) {
result += totalComponent.scaledValueFrom(components);
}
return result;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,10 @@ public double synthesizeFrom(double[] components, long timestamp) {
final var measure = new OngoingPowerMeasure(metadata, doubler);
final var testProc = new TestComponentProcessor();
// need to get updated metadata
final var doublerIndex = measure.metadata().metadataFor(doublerName).index();
final var updatedMetadata = measure.metadata();
assertThat(updatedMetadata.componentCardinality()).isEqualTo(metadata.componentCardinality() + 1);
final var doublerIndex = updatedMetadata.metadataFor(doublerName).index();
assertThat(doublerIndex).isEqualTo(metadata.componentCardinality());
measure.registerProcessorFor(doublerIndex, testProc);

final var components = new double[metadata.componentCardinality()];
Expand All @@ -133,6 +136,7 @@ public double synthesizeFrom(double[] components, long timestamp) {

assertThat(testProc.values.getFirst().value()).isEqualTo(m1c1 * 2);
assertThat(testProc.values.getLast().value()).isEqualTo(m2c1 * 2);
assertThat(PowerMeasure.asString(measure)).contains("doubler", testProc.values.toString());
}

private static class TestComponentProcessor implements ComponentProcessor {
Expand All @@ -142,6 +146,11 @@ private static class TestComponentProcessor implements ComponentProcessor {
public void recordComponentValue(double value, long timestamp) {
values.add(new PowerMeasure.TimestampedValue(timestamp, value));
}

@Override
public String output() {
return values.toString();
}
}

private static class TestMeasureProcessor implements MeasureProcessor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public String output() {
final var output = PowerMeasure.asString(stopped);
assertThat(output).contains(stoppedCompProcOutput, stoppedMeasureOutput, ongoingCompProcOutput);
// second anonymous class
assertThat(output).contains(stoppedCompProcName, stoppedMeasureProcName, "Aggregated total from (cp1)",
assertThat(output).contains(stoppedCompProcName, stoppedMeasureProcName, "total (cp1)",
getClass().getName() + "$2");
assertThat(output).contains("0.00mW");
assertThat(output).doesNotContain("Infinity");
Expand Down
Loading