Skip to content

Commit

Permalink
Add hellinger distance calculation to dbscan engine (#41)
Browse files Browse the repository at this point in the history
add hellinger distance measure to dbscan engine

o add HellingerDistanceMeasure
o default value of epsilon is not the same in AlarmInSpaceTimeDistanceMeasure (100d) and HellingerDistanceMeasure (75d)


ALEC-122
  • Loading branch information
BenjaminJ committed Sep 20, 2022
1 parent c3db51a commit b3e33c4
Show file tree
Hide file tree
Showing 29 changed files with 479 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public interface Alarm {

String getId();

long getFirstTime();

long getTime();

boolean isClear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
*/
public final class ImmutableAlarm implements Alarm {
private final String id;
private final long firstTime;
private final long time;
private final Severity severity;
private final String inventoryObjectId;
Expand All @@ -48,6 +49,7 @@ public final class ImmutableAlarm implements Alarm {

private ImmutableAlarm(Builder builder) {
this.id = builder.id;
this.firstTime = builder.firstTime;
this.time = builder.time;
this.severity = builder.severity;
this.inventoryObjectId = builder.inventoryObjectId;
Expand All @@ -59,6 +61,7 @@ private ImmutableAlarm(Builder builder) {

public static final class Builder {
private String id;
private long firstTime;
private long time;
private Severity severity;
private String inventoryObjectId;
Expand All @@ -68,11 +71,13 @@ public static final class Builder {
private Long nodeId;

private Builder() {
firstTime = System.currentTimeMillis();
time = System.currentTimeMillis();
}

private Builder(Alarm alarm) {
this.id = alarm.getId();
this.firstTime = alarm.getFirstTime();
this.time = alarm.getTime();
this.severity = alarm.getSeverity();
this.inventoryObjectId = alarm.getInventoryObjectId();
Expand All @@ -87,6 +92,11 @@ public Builder setId(String id) {
return this;
}

public Builder setFirstTime(long firstTime){
this.firstTime = firstTime;
return this;
}

public Builder setTime(long time) {
this.time = time;
return this;
Expand Down Expand Up @@ -141,6 +151,11 @@ public String getId() {
return id;
}

@Override
public long getFirstTime(){
return firstTime;
}

@Override
public long getTime() {
return time;
Expand Down Expand Up @@ -187,6 +202,7 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
ImmutableAlarm that = (ImmutableAlarm) o;
return time == that.time &&
firstTime == that.firstTime &&
Objects.equals(id, that.id) &&
severity == that.severity &&
Objects.equals(inventoryObjectId, that.inventoryObjectId) &&
Expand All @@ -205,6 +221,7 @@ public int hashCode() {
public String toString() {
return "ImmutableAlarm{" +
"id='" + id + '\'' +
", firstTime=" + firstTime +
", time=" + time +
", severity=" + severity +
", inventoryObjectId='" + inventoryObjectId + '\'' +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public static Alarm toAlarm(org.opennms.alec.datasource.v1.schema.Alarm alarm,
org.opennms.alec.datasource.v1.schema.Event event) {
return ImmutableAlarm.newBuilder()
.setId(alarm.getId())
.setFirstTime(alarm.getFirstEventTime())
.setTime(event.getTime())
.setInventoryObjectType(alarm.getInventoryObjectType())
.setInventoryObjectId(alarm.getInventoryObjectId())
Expand Down Expand Up @@ -365,7 +366,7 @@ public static Alarms toAlarms(List<Alarm> apiAlarms) {
alarm.setInventoryObjectId(apiAlarm.getInventoryObjectId());
alarm.setInventoryObjectType(apiAlarm.getInventoryObjectType());
alarm.setLastSeverity(toSeverity(apiAlarm.getSeverity()));
alarm.setFirstEventTime(apiAlarm.getTime());
alarm.setFirstEventTime(apiAlarm.getFirstTime());
alarm.setLastEventTime(apiAlarm.getTime());

// Add a single "event" to the alarm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public void canConvertToAndFromAlarms() {
Alarm a1 = ImmutableAlarm.newBuilder()
.setId("1")
.setDescription("abc")
.setFirstTime(1l)
.setInventoryObjectId("io-id")
.setInventoryObjectType("io-type")
.setTime(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public Alarm toAlarm(org.opennms.integration.api.v1.model.Alarm alarm) {
ImmutableAlarm.Builder alarmBuilder = ImmutableAlarm.newBuilder();
alarmBuilder
.setId(alarm.getReductionKey())
.setFirstTime(alarm.getFirstEventTime().getTime())
.setTime(alarm.getLastEventTime().getTime())
.setSeverity(toSeverity(alarm.getSeverity()))
.setInventoryObjectId(alarm.getManagedObjectInstance())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public void canScopeLinkDownAlarms() {
.build();
org.opennms.integration.api.v1.model.Alarm apiAlarm = ImmutableAlarm.newBuilder()
.setId(43)
.setFirstEventTime(new Date(0))
.setReductionKey("boo:1")
.setLastEventTime(new Date(0))
.setManagedObjectType(ManagedObjectType.SnmpInterface.getName())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

public interface DistanceMeasure extends org.apache.commons.math3.ml.distance.DistanceMeasure {

double compute(double timeA, double timeB, double spatialDistance);
double compute(double timeA, double timeB, double firstTimeA, double firstTimeB, double spatialDistance);

double getAlpha();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -766,21 +766,23 @@ private long getVertexIdForAlarm(Alarm alarm) {
* @param distance spatial distance between alarm1 and alarm2
* @return effective distance between alarm1 and alarm2
*/
public double getDistanceBetween(double t1, double t2, double distance) {
public double getDistanceBetween(double t1, double t2, double firstTimeA, double firstTimeB, double distance) {
return Math.abs(t2 - t1) + distance;
}

private Alarm getClosestNeighborInSituation(Alarm alarm, List<Alarm> candidates) {
final long vertexIdA = getVertexIdForAlarm(alarm);
final double timeA = alarm.getTime();
final double firstTimeA = alarm.getFirstTime();

return candidates.stream()
.map(candidate -> {
final double timeB = candidate.getTime();
final double firstTimeB = candidate.getFirstTime();
final long vertexIdB = getVertexIdForAlarm(candidate);
final double spatialDistance = vertexIdA == vertexIdB ? 0 : getSpatialDistanceBetween(vertexIdA,
vertexIdB);
final double distance = getDistanceBetween(timeA, timeB, spatialDistance);
final double distance = getDistanceBetween(timeA, timeB, firstTimeA, firstTimeB, spatialDistance);
return new CandidateAlarmWithDistance(candidate, distance);
})
.min(Comparator.comparingDouble(CandidateAlarmWithDistance::getDistance)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class AlarmInSpaceTime implements Clusterable {
public AlarmInSpaceTime(CEVertex vertex, Alarm alarm) {
this.vertex = Objects.requireNonNull(vertex);
this.alarm = Objects.requireNonNull(alarm);
point = new double[]{alarm.getTime(), vertex.getNumericId()};
point = new double[]{alarm.getTime(), vertex.getNumericId(), alarm.getFirstTime()};
}

@Override
Expand All @@ -61,6 +61,10 @@ public long getAlarmTime() {
return alarm.getTime();
}

public long getAlarmFirstTime() {
return alarm.getFirstTime();
}

public CEVertex getVertex() {
return vertex;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,10 @@
import org.apache.commons.math3.exception.DimensionMismatchException;
import org.opennms.alec.engine.api.DistanceMeasure;
import org.opennms.alec.engine.cluster.SpatialDistanceCalculator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AlarmInSpaceTimeDistanceMeasure implements DistanceMeasure {
private static final Logger LOG = LoggerFactory.getLogger(AlarmInSpaceTimeDistanceMeasure.class);
public static final double DEFAULT_EPSILON = 100d;

private final SpatialDistanceCalculator spatialDistanceCalculator;
private final double alpha;
private final double beta;
Expand All @@ -50,12 +49,13 @@ public AlarmInSpaceTimeDistanceMeasure(SpatialDistanceCalculator SpatialDistance
this.beta = beta;
}

@Override
public double compute(double[] a, double[] b) throws DimensionMismatchException {
final double timeA = a[0];
final double timeB = b[0];

final long vertexIdA = (long)a[1];
final long vertexIdB = (long)b[1];
final long vertexIdA = (long) a[1];
final long vertexIdB = (long) b[1];

double spatialDistance = 0;
if (vertexIdA != vertexIdB) {
Expand All @@ -67,17 +67,17 @@ public double compute(double[] a, double[] b) throws DimensionMismatchException
}

final double distance = compute(timeA, timeB, spatialDistance);
/* Too noisy - even for trace
if (LOG.isTraceEnabled()) {
LOG.trace("v1: {}, v2: {}, d({},{},{}) = {}", vertexIdA, vertexIdB, timeA, timeB, spatialDistance, distance);
}
*/

return distance;
}

@Override
public double compute(double timeA, double timeB, double spatialDistance) {
return alpha * ( beta * (Math.abs(timeA - timeB) / 1000d / 60d) + (1-beta) * spatialDistance / DEFAULT_WEIGHT);
return alpha * (beta * (Math.abs(timeA - timeB) / 1000d / 60d) + (1 - beta) * spatialDistance / DEFAULT_WEIGHT);
}

@Override
public double compute(double timeA, double timeB, double firstTimeA, double firstTimeB, double spatialDistance) {
return compute(timeA, timeB, spatialDistance);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@
public class DBScanEngine extends AbstractClusterEngine {
private static final Logger LOG = LoggerFactory.getLogger(DBScanEngine.class);

public static final double DEFAULT_EPSILON = 100d;
public static final double DEFAULT_ALPHA = 144.47117699d;
public static final double DEFAULT_BETA = 0.55257784d;
public static final String DEFAULT_DISTANCE_MEASURE = "alarminspaceandtimedistance";
Expand Down Expand Up @@ -110,8 +109,8 @@ public List<Cluster<AlarmInSpaceTime>> cluster(long timestampInMillis, Graph<CEV
}

@Override
public double getDistanceBetween(double t1, double t2, double distance) {
return distanceMeasure.compute(t1, t2, distance);
public double getDistanceBetween(double t1, double t2, double firstTimeA, double firstTimeB, double distance) {
return distanceMeasure.compute(t1, t2, firstTimeA, firstTimeB, distance);
}

public DistanceMeasure getDistanceMeasure() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2018 The OpenNMS Group, Inc.
* OpenNMS(R) is Copyright (C) 1999-2018 The OpenNMS Group, Inc.
*
* OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
*
* OpenNMS(R) is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* OpenNMS(R) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with OpenNMS(R). If not, see:
* http://www.gnu.org/licenses/
*
* For more information contact:
* OpenNMS(R) Licensing <license@opennms.org>
* http://www.opennms.org/
* http://www.opennms.com/
*******************************************************************************/

package org.opennms.alec.engine.dbscan;

import static org.opennms.alec.datasource.api.InventoryObject.DEFAULT_WEIGHT;

import java.util.Objects;

import org.apache.commons.math3.exception.DimensionMismatchException;
import org.opennms.alec.engine.api.DistanceMeasure;
import org.opennms.alec.engine.cluster.SpatialDistanceCalculator;

public class HellingerDistanceMeasure implements DistanceMeasure {
public static final double DEFAULT_EPSILON = 75d;

private final SpatialDistanceCalculator spatialDistanceCalculator;
private final double alpha;
private final double beta;

public HellingerDistanceMeasure(SpatialDistanceCalculator SpatialDistanceCalculator, double alpha, double beta) {
this.spatialDistanceCalculator = Objects.requireNonNull(SpatialDistanceCalculator);
this.alpha = alpha;
this.beta = beta;
}

@Override
public double compute(double[] a, double[] b) throws DimensionMismatchException {
final double timeA = a[0];
final double timeB = b[0];
final double firstTimeA = a[2];
final double firstTimeB = b[2];
final long vertexIdA = (long) a[1];
final long vertexIdB = (long) b[1];

double spatialDistance = 0;
if (vertexIdA != vertexIdB) {
spatialDistance = spatialDistanceCalculator.getSpatialDistanceBetween(vertexIdA, vertexIdB);
if (spatialDistance == 0) {
// No path
spatialDistance = Integer.MAX_VALUE;
}
}

final double distance = compute(timeA, timeB, firstTimeA, firstTimeB, spatialDistance);

return distance;
}

@Override
public double compute(double timeA, double timeB, double firstTimeA, double firstTimeB, double spatialDistance) {
double w = 4851.28;
double bias = -1986.00;
double var_a = Math.pow(((timeA - firstTimeA) * w) + bias, 2);
double mean_a = 0.5 * (timeA + firstTimeA);
double var_b = Math.pow(((timeB - firstTimeB) * w) + bias, 2);
double mean_b = 0.5 * (timeB + firstTimeB);
double var_sum = var_a + var_b;
double eps_for_grad_sqrt = 1.0e-32;
double hellinger = Math.sqrt(1 - Math.sqrt((2 * Math.sqrt(var_a) * Math.sqrt(var_b)) / var_sum) * Math.exp(-0.25 * Math.pow(mean_a - mean_b, 2) / var_sum) + eps_for_grad_sqrt);

return alpha * (beta * (Math.abs(timeA - timeB) / 1000d / 60d) + (1 - beta) * spatialDistance / DEFAULT_WEIGHT) * (1 + hellinger);
}

@Override
public double getAlpha() {
return alpha;
}

@Override
public double getBeta() {
return beta;
}

@Override
public String getName() {
return "hellinger";
}
}
Loading

0 comments on commit b3e33c4

Please sign in to comment.