From 5869b4ddcd3e1841a5369517a7b14c4c73cf0531 Mon Sep 17 00:00:00 2001 From: Alexandre <alexandre.perles@gmail.com> Date: Fri, 7 Jun 2024 09:54:21 +0200 Subject: [PATCH] Fix Groovy dependencies, Add example, Fix fusion ambiguity --- README.md | 50 ++++- build.gradle | 4 +- .../groovy/TemperatureClustering.groovy | 187 ------------------ .../TwoDimensionsPointsClustering.groovy | 14 ++ .../smac/amas4dc/amas/agent/ClusterAgent.java | 4 +- .../amas4dc/cluster/datapoint/DataPoint.java | 7 + .../cluster/datapoint/DataPointFuser.java | 3 +- .../evaluation/CalinskiHarabazIndex.java | 2 +- src/test/groovy/SimpleClusteringIT.groovy | 20 +- 9 files changed, 94 insertions(+), 197 deletions(-) delete mode 100644 src/experiments/groovy/TemperatureClustering.groovy diff --git a/README.md b/README.md index 9535b96..0a06931 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,55 @@ project(':amas4dc').projectDir = new File(settingsDir, '../amas4dc') ## Usage example ```java -var amas4dc = new AMAS4DC<CustomDataPoint>(new MASSettings(new CustomDataPointDistanceMethod(), 0.5f, EnumSet.noneOf(AMASOption), new CustomDataPointFuser())); +var amas4dc = new AMAS4DC<CustomDataPoint>(new MASSettings(new CustomDataPointDistanceMethod(), 0.5f, EnumSet.noneOf(AMASOption.class), new CustomDataPointFuser())); -amas4dc.fit(List.of(new CustomDataPoint(1), new CustomDataPoint(2))); +amas4dc.fit(List.of(new CustomDataPoint(LocalDateTime.now().minusDays(1), 1.0f), new CustomDataPoint(LocalDateTime.now(), 2.0f))); var results = amas4dc.retrieveClusters(); +System.out.println("Clusters count: " + results.clusters().size()); +``` + +```java +public class CustomDataPoint implements DataPoint { + + private final LocalDateTime eventDateTime; + + @Getter + @Setter + private float value; + + public CustomDataPoint(LocalDateTime eventDateTime, float value) { + this.eventDateTime = eventDateTime; + this.value = value; + } + + @Override + public LocalTime getEventTime() { + return eventDateTime.toLocalTime(); + } + + @Override + public LocalDate getEventDate() { + return eventDateTime.toLocalDate(); + } +} + +public class CustomDataPointFuser implements DataPointFuser<CustomDataPoint> { + @Override + public void accept(CustomDataPoint current, CustomDataPoint other) { + current.setValue((current.getValue() + other.getValue()) / 2); + } +} + +public class CustomDataPointDistanceMethod implements DistanceMethod<CustomDataPoint> { + @Override + public float apply(CustomDataPoint dp1, CustomDataPoint dp2) { + return Math.abs(dp1.getValue() - dp2.getValue()); + } + + @Override + public boolean areRoughlySimilar(CustomDataPoint dp1, CustomDataPoint dp2) { + return true; + } +} ``` \ No newline at end of file diff --git a/build.gradle b/build.gradle index 67710fc..cdbea65 100644 --- a/build.gradle +++ b/build.gradle @@ -42,8 +42,8 @@ dependencies { // https://mvnrepository.com/artifact/org.projectlombok/lombok compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.30' annotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.30' - testImplementation 'org.spockframework:spock-core:2.4-M1-groovy-4.0' - testImplementation 'org.codehaus.groovy:groovy-all:3.0.16' + testImplementation 'org.spockframework:spock-core:2.3-groovy-3.0' + testImplementation 'org.codehaus.groovy:groovy-all:3.0.8' testImplementation 'net.bytebuddy:byte-buddy:1.14.2' testImplementation 'org.objenesis:objenesis:3.2' } diff --git a/src/experiments/groovy/TemperatureClustering.groovy b/src/experiments/groovy/TemperatureClustering.groovy deleted file mode 100644 index cecc709..0000000 --- a/src/experiments/groovy/TemperatureClustering.groovy +++ /dev/null @@ -1,187 +0,0 @@ -import fr.irit.smac.amas4dc.AMAS4DC -import fr.irit.smac.amas4dc.amas.AMASOption -import fr.irit.smac.amas4dc.amas.MASSettings -import fr.irit.smac.amas4dc.cluster.Cluster -import fr.irit.smac.amas4dc.cluster.datapoint.DataPoint -import fr.irit.smac.amas4dc.cluster.datapoint.DataPointFuser -import fr.irit.smac.amas4dc.cluster.distance.DistanceMethod -import fr.irit.smac.lxplot.LxPlot -import groovy.transform.ToString - -import javax.swing.* -import java.awt.* -import java.util.List - -class TemperatureClustering { - static void main(String[] args) { - - def amas4dc = new AMAS4DC<TemperatureDataPoint>(new MASSettings<TemperatureDataPoint>(new TemperatureDistanceMethod(), - 0.5f, - EnumSet.of(AMASOption.KeepAllDataPoints), - new TemperatureDataPointFuser())) - -// 11 juin 2023 -// https://www.infoclimat.fr/observations-meteo/archives/11/juin/2023/paris-montsouris/07156.html - def initialData = [ - new TemperatureDataPoint(21.9, 0), - new TemperatureDataPoint(21.8, 1), - new TemperatureDataPoint(21.7, 2), - new TemperatureDataPoint(21.2, 3), - new TemperatureDataPoint(21.1, 4), - new TemperatureDataPoint(20.6, 5), - new TemperatureDataPoint(20.5, 6), - new TemperatureDataPoint(20.8, 7), - new TemperatureDataPoint(21.3, 8), - new TemperatureDataPoint(22.6, 9), - new TemperatureDataPoint(23.9, 10), - new TemperatureDataPoint(24.9, 11), - new TemperatureDataPoint(25.7, 12), - new TemperatureDataPoint(26.8, 13), - new TemperatureDataPoint(25.8, 14), - new TemperatureDataPoint(25.4, 15), - new TemperatureDataPoint(24.8, 16), - new TemperatureDataPoint(24.9, 17), - new TemperatureDataPoint(24.1, 18), - new TemperatureDataPoint(23.9, 19), - new TemperatureDataPoint(24.1, 20), - new TemperatureDataPoint(22.1, 21), - new TemperatureDataPoint(18.9, 22), - new TemperatureDataPoint(18.8, 23) - ] - - - def frame = new JFrame() - def plotter = new Plotter() - frame.setContentPane(plotter) - frame.setSize(1400, 700) - frame.setVisible(true) - - for (i in 0..<initialData.size()) { - def data = initialData.subList(i, i + 1) - plotter.renderInputData(initialData.subList(0, i + 1)) - amas4dc.fit(data) - var results = amas4dc.retrieveExtendedClusters() - println(results) - plotter.renderClusters(results.clusters()) - LxPlot.getChart("Silhouette distance").add(i, results.silhouetteScore()) - sleep(1000) - } - } - - @ToString - static class TemperatureDataPoint implements DataPoint { - double degrees - double startTime, endTime - double averageTime - - TemperatureDataPoint(double degrees, double startTime, double endTime) { - this.degrees = degrees - this.startTime = startTime - this.endTime = endTime - this.averageTime = (startTime + endTime) / 2 - } - - TemperatureDataPoint(double degrees, double time) { - this(degrees, time, time) - } - } - - static class TemperatureDataPointFuser implements DataPointFuser<TemperatureDataPoint> { - - @Override - TemperatureDataPoint apply(TemperatureDataPoint dp1, TemperatureDataPoint dp2) { - def newStartTime = dp1.startTime - def newEndTime = dp1.endTime - if (dp2.startTime < newStartTime) - newStartTime = dp2.startTime - if (dp2.endTime > newEndTime) - newEndTime = dp2.endTime - return new TemperatureDataPoint(dp1.degrees * 0.9 + dp2.degrees * 0.1, newStartTime, newEndTime) - } - } - - static class TemperatureDistanceMethod implements DistanceMethod<TemperatureDataPoint> { - - @Override - float apply(TemperatureDataPoint dp1, TemperatureDataPoint dp2) { - var temporallySimilar = false - if (dp1.endTime < dp2.startTime) { - if (dp2.startTime - dp1.endTime < 2) - temporallySimilar = true - else - temporallySimilar = false - } else if (dp2.endTime < dp1.startTime) { - if (dp1.startTime - dp2.endTime < 2) - temporallySimilar = true - else - temporallySimilar = false - } - if (Math.abs(dp1.degrees - dp2.degrees) < 1 && temporallySimilar) - return 0; - return 1; - } - - @Override - boolean areRoughlySimilar(TemperatureDataPoint dp1, TemperatureDataPoint dp2) { - return true - } - } - - static class Plotter extends JPanel { - ArrayList<TemperatureDataPoint> inputData - List<Cluster<TemperatureDataPoint>> clusters - Map<String, Color> clustersColors = new HashMap<>() - - @Override - protected void paintComponent(Graphics g) { - var g2d = (Graphics2D) g - - if (inputData == null) - return - g2d.setColor(Color.white) - g2d.fillRect(0, 0, 1400, 700) - g2d.setColor(Color.BLACK) - g2d.drawLine(timeToX(0), degreeToY(0), timeToX(24), degreeToY(0)) - g2d.drawLine(timeToX(0), degreeToY(0), timeToX(0), degreeToY(30)) - - for (def tdp : inputData) { - g2d.drawOval(timeToX(tdp.startTime) - 3, degreeToY(tdp.degrees) - 3, 6, 6) - } - def random = new Random() - for (def c : clusters) { - def color = clustersColors.computeIfAbsent(c.id, id -> new Color(random.nextFloat(), random.nextFloat(), random.nextFloat())) - g2d.setColor(Color.BLUE) - g2d.setStroke(new BasicStroke(2)) - g2d.drawLine(timeToX(c.representative.startTime), degreeToY(c.representative.degrees), timeToX(c.representative.endTime), degreeToY(c.representative.degrees)) - } - g2d.setColor(Color.BLACK) - for (i in 0..<24) { - g2d.drawString(i + "", timeToX(i), 665) - } - g2d.drawString("Hour of the day", timeToX(24), 665) - for (i in 0..<30) { - g2d.drawString(i + "", 10, degreeToY(i)) - } - g2d.drawString("Temperature °C", 10, degreeToY(30)) - - } - - int timeToX(double time) { - return (int) (time * 40) + 50 - } - - int degreeToY(double degree) { - return 650 - (int) (degree * 20) - } - - void renderInputData(List<TemperatureDataPoint> inputDataList) { - this.inputData = inputDataList - repaint(); - } - - void renderClusters(List<Cluster<TemperatureDataPoint>> clusters) { - this.clusters = clusters - repaint() - } - } -} diff --git a/src/experiments/groovy/TwoDimensionsPointsClustering.groovy b/src/experiments/groovy/TwoDimensionsPointsClustering.groovy index 79eb2e4..0d39d86 100644 --- a/src/experiments/groovy/TwoDimensionsPointsClustering.groovy +++ b/src/experiments/groovy/TwoDimensionsPointsClustering.groovy @@ -9,6 +9,9 @@ import fr.irit.smac.lxplot.commons.ChartType import groovy.transform.ToString import java.awt.geom.Point2D +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime class TwoDimensionsPointsClustering { static void main(String[] args) { @@ -42,9 +45,20 @@ class TwoDimensionsPointsClustering { @ToString static class TwoDimensionsDataPoint implements DataPoint { Point2D.Double point2D + LocalDateTime time TwoDimensionsDataPoint(Point2D.Double point2D) { this.point2D = point2D + this.time = LocalDateTime.now() + } + @Override + LocalTime getEventTime() { + return this.time.toLocalTime() + } + + @Override + LocalDate getEventDate() { + return this.time.toLocalDate() } } diff --git a/src/main/java/fr/irit/smac/amas4dc/amas/agent/ClusterAgent.java b/src/main/java/fr/irit/smac/amas4dc/amas/agent/ClusterAgent.java index 4f5a814..ae4f051 100644 --- a/src/main/java/fr/irit/smac/amas4dc/amas/agent/ClusterAgent.java +++ b/src/main/java/fr/irit/smac/amas4dc/amas/agent/ClusterAgent.java @@ -164,7 +164,7 @@ public class ClusterAgent<T extends DataPoint> extends Agent<DynamicClusteringAM if (logger.isLoggable(Level.INFO)) logger.info(this + " absorbs the data point " + otherDataPoint); - amas.getMasSettings().dataPointFuser().apply(cluster.getRepresentative(), otherDataPoint); + amas.getMasSettings().dataPointFuser().accept(cluster.getRepresentative(), otherDataPoint); cluster.addDataPoint(otherDataPoint); } @@ -172,7 +172,7 @@ public class ClusterAgent<T extends DataPoint> extends Agent<DynamicClusteringAM if (logger.isLoggable(Level.INFO)) logger.info(this + " absorbs the cluster " + otherCluster); - amas.getMasSettings().dataPointFuser().apply(cluster.getRepresentative(), otherCluster.getRepresentative()); + amas.getMasSettings().dataPointFuser().accept(cluster.getRepresentative(), otherCluster.getRepresentative()); if (getAmas().getMasSettings().amasOptions().contains(AMASOption.KeepAllDataPoints)) ((ExtendedCluster<T>) cluster).addClusterContent((ExtendedCluster<T>) otherCluster); diff --git a/src/main/java/fr/irit/smac/amas4dc/cluster/datapoint/DataPoint.java b/src/main/java/fr/irit/smac/amas4dc/cluster/datapoint/DataPoint.java index 5199f76..f32c582 100644 --- a/src/main/java/fr/irit/smac/amas4dc/cluster/datapoint/DataPoint.java +++ b/src/main/java/fr/irit/smac/amas4dc/cluster/datapoint/DataPoint.java @@ -1,7 +1,14 @@ package fr.irit.smac.amas4dc.cluster.datapoint; +import java.time.LocalDate; +import java.time.LocalTime; + public interface DataPoint { default int getBucketId() { return 0; } + + LocalTime getEventTime(); + + LocalDate getEventDate(); } diff --git a/src/main/java/fr/irit/smac/amas4dc/cluster/datapoint/DataPointFuser.java b/src/main/java/fr/irit/smac/amas4dc/cluster/datapoint/DataPointFuser.java index 90788b8..cc3ea79 100644 --- a/src/main/java/fr/irit/smac/amas4dc/cluster/datapoint/DataPointFuser.java +++ b/src/main/java/fr/irit/smac/amas4dc/cluster/datapoint/DataPointFuser.java @@ -1,6 +1,7 @@ package fr.irit.smac.amas4dc.cluster.datapoint; +import java.util.function.BiConsumer; import java.util.function.BiFunction; -public interface DataPointFuser<T extends DataPoint> extends BiFunction<T, T, T> { +public interface DataPointFuser<T extends DataPoint> extends BiConsumer<T, T> { } diff --git a/src/main/java/fr/irit/smac/amas4dc/cluster/evaluation/CalinskiHarabazIndex.java b/src/main/java/fr/irit/smac/amas4dc/cluster/evaluation/CalinskiHarabazIndex.java index 9c95f33..d2826f5 100644 --- a/src/main/java/fr/irit/smac/amas4dc/cluster/evaluation/CalinskiHarabazIndex.java +++ b/src/main/java/fr/irit/smac/amas4dc/cluster/evaluation/CalinskiHarabazIndex.java @@ -52,7 +52,7 @@ public class CalinskiHarabazIndex<T extends DataPoint> { private T computeGlobalCentroid(List<ExtendedCluster<T>> clusters, MASSettings<T> masSettings) { var fused = clusters.get(0).getRepresentative(); for (int i = 1; i < clusters.size(); i++) { - fused = masSettings.dataPointFuser().apply(fused, clusters.get(i).getRepresentative()); + masSettings.dataPointFuser().accept(fused, clusters.get(i).getRepresentative()); } return fused; } diff --git a/src/test/groovy/SimpleClusteringIT.groovy b/src/test/groovy/SimpleClusteringIT.groovy index fdbd112..57d39ef 100644 --- a/src/test/groovy/SimpleClusteringIT.groovy +++ b/src/test/groovy/SimpleClusteringIT.groovy @@ -6,6 +6,10 @@ import fr.irit.smac.amas4dc.cluster.datapoint.DataPointDatabase import fr.irit.smac.amas4dc.cluster.distance.DistanceMethod import spock.lang.Specification +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime + class SimpleClusteringIT extends Specification { def "Clustering of two similar 1D objects"() { def database = new DataPointDatabase<OneDDataPoint>() @@ -25,10 +29,22 @@ class SimpleClusteringIT extends Specification { } class OneDDataPoint implements DataPoint { - public final double value + private final double value + private final LocalDateTime time OneDDataPoint(double value) { - this.value = value; + this.value = value + this.time = LocalDateTime.now() + } + + @Override + LocalTime getEventTime() { + return this.time.toLocalTime() + } + + @Override + LocalDate getEventDate() { + return this.time.toLocalDate() } } -- GitLab