diff --git a/.gitignore b/.gitignore index b63da4551b2e169d28b20728d5d53c6ca1779da4..f8e56bdf97ea660da3dd96d8c65d49b59e05146e 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,5 @@ bin/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store +cache diff --git a/.idea/groovyc.xml b/.idea/groovyc.xml new file mode 100644 index 0000000000000000000000000000000000000000..c0981345324314a461959f5d06413f2824660ff2 --- /dev/null +++ b/.idea/groovyc.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="GroovyCompilerProjectConfiguration"> + <option name="invokeDynamic" value="true" /> + </component> +</project> \ No newline at end of file diff --git a/build.gradle b/build.gradle index d3f92fa60afd4b8809a5951c37855ad2497d547b..bf1a5fd186c16a465a7ea492ba18510391f07b2e 100644 --- a/build.gradle +++ b/build.gradle @@ -11,12 +11,31 @@ repositories { maven { url "https://jitpack.io" } } +sourceSets { + experiments { + groovy.srcDir "$projectDir/src/experiments/groovy" + resources.srcDir "$projectDir/src/experiments/resources" + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + } +} +configurations { + experimentsImplementation.extendsFrom testImplementation + experimentsRuntime.extendsFrom testRuntime +} +tasks.register('experimentsTest', Test) { + group = "verification" + testClassesDirs = sourceSets.experiments.output.classesDirs + classpath = sourceSets.experiments.runtimeClasspath +} + dependencies { // AMAK (Only one of the next two lines should be uncommented. Also, settings.gradle file should also be modified) implementation 'com.github.alexandreprl:amak:3.1.0' // Uncomment this line to get AMAK from git repository (The last part matches a tag, a commit hash or the last commit of a branch : branchName-SNAPSHOT // implementation project(':amak') // Uncomment this line to get AMAK from local - + implementation 'com.github.alexandreprl:lxplot:main-SNAPSHOT' + testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation 'org.junit.jupiter:junit-jupiter' diff --git a/src/experiments/groovy/TemperatureClustering.groovy b/src/experiments/groovy/TemperatureClustering.groovy new file mode 100644 index 0000000000000000000000000000000000000000..86b955cfaf1576cd163747e327b210f8bad91397 --- /dev/null +++ b/src/experiments/groovy/TemperatureClustering.groovy @@ -0,0 +1,186 @@ +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 +import fr.irit.smac.amas4dc.cluster.DataPointFuser +import fr.irit.smac.amas4dc.cluster.SimilarityScoreMethod +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 TemperatureSimilarityMethod(), + 0.5f, + EnumSet.noneOf(AMASOption), + 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.retrieveClusters() + println(results) + plotter.renderClusters(results.clusters()) + 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 TemperatureSimilarityMethod implements SimilarityScoreMethod<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 1; + return 0; + } + + @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 new file mode 100644 index 0000000000000000000000000000000000000000..31d9bbb526bcc3c142b6c47bb41e281d3e846ee6 --- /dev/null +++ b/src/experiments/groovy/TwoDimensionsPointsClustering.groovy @@ -0,0 +1,65 @@ +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 +import fr.irit.smac.amas4dc.cluster.SimilarityScoreMethod +import fr.irit.smac.lxplot.LxPlot +import fr.irit.smac.lxplot.commons.ChartType +import groovy.transform.ToString + +import java.awt.geom.Point2D + +class TwoDimensionsPointsClustering { + static void main(String[] args) { + + def amas4dc = new AMAS4DC<TwoDimensionsDataPoint>(new MASSettings(new TwoDimensionsSimilarityMethod(), 0.5f, EnumSet.noneOf(AMASOption), (TwoDimensionsDataPoint dp1, TwoDimensionsDataPoint dp2) -> { + return new TwoDimensionsDataPoint(new Point2D.Double((dp1.getPoint2D().getX() + dp2.getPoint2D().getX()) / 2, + (dp1.getPoint2D().getY() + dp2.getPoint2D().getY()) / 2)) + })) + + LxPlot.getChart("Data", ChartType.PLOT); + def initialData = [new TwoDimensionsDataPoint(new Point2D.Double(1.0, 1.0)), new TwoDimensionsDataPoint(new Point2D.Double(2.0, 2.0))] + render(initialData) + amas4dc.fit(initialData) + var results = amas4dc.retrieveClusters() + println(results) + render(results.clusters()) + } + + static void render(ArrayList<TwoDimensionsDataPoint> twoDimensionsDataPoints) { + for (def tddp : twoDimensionsDataPoints) { + LxPlot.getChart("Data").add("Initial", tddp.point2D.getX(), tddp.getPoint2D().getY()) + } + } + + static void render(List<Cluster<TwoDimensionsDataPoint>> clusters) { + for (def c : clusters) { + LxPlot.getChart("Data").add("Cluster representative point", c.getRepresentative().point2D.getX(), c.getRepresentative().point2D.getY()) + } + } + + @ToString + static class TwoDimensionsDataPoint implements DataPoint { + Point2D.Double point2D + + TwoDimensionsDataPoint(Point2D.Double point2D) { + this.point2D = point2D + } + } + + static class TwoDimensionsSimilarityMethod implements SimilarityScoreMethod<TwoDimensionsDataPoint> { + + @Override + float apply(TwoDimensionsDataPoint dp1, TwoDimensionsDataPoint dp2) { + if (dp1.point2D.distance(dp2.point2D) < 10) + return 1; + return 0 + } + + @Override + boolean areRoughlySimilar(TwoDimensionsDataPoint dp1, TwoDimensionsDataPoint dp2) { + return true + } + } +}