Skip to content
Snippets Groups Projects
Commit 5869b4dd authored by Alexandre's avatar Alexandre
Browse files

Fix Groovy dependencies, Add example, Fix fusion ambiguity

parent b41aaa39
Branches
No related tags found
No related merge requests found
......@@ -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
......@@ -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'
}
......
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()
}
}
}
......@@ -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()
}
}
......
......@@ -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);
......
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();
}
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> {
}
......@@ -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;
}
......
......@@ -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()
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment