Skip to content

Commit 99ff94e

Browse files
committed
feat: add TotalSyntheticComponent
1 parent 90b89b0 commit 99ff94e

File tree

2 files changed

+94
-1
lines changed

2 files changed

+94
-1
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package net.laprun.sustainability.power.analysis;
2+
3+
import java.util.Arrays;
4+
import java.util.Objects;
5+
import java.util.function.Function;
6+
import java.util.stream.Collectors;
7+
8+
import net.laprun.sustainability.power.Errors;
9+
import net.laprun.sustainability.power.SensorMetadata;
10+
import net.laprun.sustainability.power.SensorUnit;
11+
12+
public class TotalSyntheticComponent implements SyntheticComponent {
13+
private final Function<double[], Double> formula;
14+
private final SensorUnit expectedResultUnit;
15+
private final SensorMetadata.ComponentMetadata metadata;
16+
17+
public TotalSyntheticComponent(SensorMetadata metadata, SensorUnit expectedResultUnit, int... totalComponentIndices) {
18+
Objects.requireNonNull(totalComponentIndices, "Must specify component indices that will aggregated in a total");
19+
this.expectedResultUnit = Objects.requireNonNull(expectedResultUnit, "Must specify expected result unit");
20+
21+
final var errors = new Errors();
22+
final var totalComponents = Arrays.stream(totalComponentIndices)
23+
.mapToObj(i -> toTotalComponent(metadata, i, errors))
24+
.toArray(TotalComponent[]::new);
25+
final String description = Arrays.stream(totalComponents)
26+
.map(TotalComponent::name)
27+
.collect(Collectors.joining(" + ", "Aggregated total from (", ")"));
28+
final String name = Arrays.stream(totalComponents)
29+
.map(TotalComponent::name)
30+
.collect(Collectors.joining("_", "total", ""));
31+
final var isAttributed = metadata.components().values().stream()
32+
.map(SensorMetadata.ComponentMetadata::isAttributed)
33+
.reduce(Boolean::logicalAnd).orElse(false);
34+
formula = components -> {
35+
double result = 0;
36+
for (var totalComponent : totalComponents) {
37+
result += components[totalComponent.index] * totalComponent.factor;
38+
}
39+
return result;
40+
};
41+
42+
if (metadata.exists(name)) {
43+
errors.addError("Component " + name + " already exists");
44+
}
45+
46+
if (errors.hasErrors()) {
47+
throw new IllegalArgumentException(errors.formatErrors());
48+
}
49+
50+
this.metadata = new SensorMetadata.ComponentMetadata(name, description, isAttributed, expectedResultUnit);
51+
}
52+
53+
private double convertToExpectedUnit(double value) {
54+
return value * expectedResultUnit.base().conversionFactorTo(expectedResultUnit);
55+
}
56+
57+
private record TotalComponent(String name, int index, double factor) {
58+
}
59+
60+
private TotalComponent toTotalComponent(SensorMetadata metadata, int index, Errors errors) {
61+
final var cm = metadata.metadataFor(index);
62+
final var name = cm.name();
63+
final var unit = cm.unit();
64+
if (!unit.isCommensurableWith(expectedResultUnit)) {
65+
errors.addError("Component " + name
66+
+ " is not commensurable with the expected base unit: " + expectedResultUnit);
67+
}
68+
69+
final var factor = unit.factor();
70+
return new TotalComponent(name, index, factor);
71+
}
72+
73+
@Override
74+
public SensorMetadata.ComponentMetadata metadata() {
75+
return metadata;
76+
}
77+
78+
@Override
79+
public double synthesizeFrom(double[] components, long timestamp) {
80+
return convertToExpectedUnit(formula.apply(components));
81+
}
82+
}

measure/src/test/java/net/laprun/sustainability/power/analysis/TotalMeasureProcessorTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@ void totalShouldFailIfAllComponentsAreNotCommensurable() {
2020
.build();
2121

2222
final var expectedResultUnit = SensorUnit.W;
23-
final var e = assertThrows(IllegalArgumentException.class,
23+
var e = assertThrows(IllegalArgumentException.class,
2424
() -> new TotalMeasureProcessor(metadata, expectedResultUnit, 0, 1));
2525
assertTrue(e.getMessage().contains("Component " + inError
2626
+ " is not commensurable with the expected base unit: " + expectedResultUnit));
27+
28+
e = assertThrows(IllegalArgumentException.class,
29+
() -> new TotalSyntheticComponent(metadata, expectedResultUnit, 0, 1));
30+
assertTrue(e.getMessage().contains("Component " + inError
31+
+ " is not commensurable with the expected base unit: " + expectedResultUnit));
2732
}
2833

2934
@Test
@@ -48,22 +53,28 @@ void testTotal() {
4853
final var m3total = m3c1 + m3c2 + m3c3;
4954

5055
final var totalProc = new TotalMeasureProcessor(metadata, SensorUnit.of("mW"), 0, 1, 2);
56+
final var totalSyncComp = new TotalSyntheticComponent(metadata, SensorUnit.W, 0, 1, 2);
5157

5258
final var components = new double[metadata.componentCardinality()];
5359
components[0] = m1c1;
5460
components[1] = m1c2;
5561
components[2] = m1c3;
5662
totalProc.recordMeasure(components, System.currentTimeMillis());
63+
// original components use mW as unit but we're asking for a synthetic total in W so the resulting total should be factored
64+
final var mWtoWFactor = SensorUnit.mW.conversionFactorTo(SensorUnit.W);
65+
assertEquals(m1total * mWtoWFactor, totalSyncComp.synthesizeFrom(components, 0));
5766

5867
components[0] = m2c1;
5968
components[1] = m2c2;
6069
components[2] = m2c3;
6170
totalProc.recordMeasure(components, System.currentTimeMillis());
71+
assertEquals(m2total * mWtoWFactor, totalSyncComp.synthesizeFrom(components, 0));
6272

6373
components[0] = m3c1;
6474
components[1] = m3c2;
6575
components[2] = m3c3;
6676
totalProc.recordMeasure(components, System.currentTimeMillis());
77+
assertEquals(m3total * mWtoWFactor, totalSyncComp.synthesizeFrom(components, 0));
6778

6879
assertEquals(m1c1 + m1c2 + m1c3 + m2c1 + m2c2 + m2c3 + m3c1 + m3c2 + m3c3, totalProc.total());
6980
assertEquals(Stream.of(m1total, m2total, m3total).min(Double::compareTo).orElseThrow(), totalProc.minMeasuredTotal());

0 commit comments

Comments
 (0)