From acc182eb26c830928a07199eb9ee0780e7b407e9 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Mon, 10 Feb 2025 14:19:25 +0700 Subject: [PATCH] Greatly improve performance of clusterization by hinting that min/max should be performed with implied zero value --- .../blockentity/EnergyCounterRenderer.kt | 2 +- .../mc/otm/core/math/Clustering.kt | 24 +++++++++++++++++-- .../mc/otm/core/util/Formatting.kt | 6 ++--- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/EnergyCounterRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/EnergyCounterRenderer.kt index 900e3a550..2c1b70a67 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/EnergyCounterRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/EnergyCounterRenderer.kt @@ -104,7 +104,7 @@ class EnergyCounterRenderer(private val context: BlockEntityRendererProvider.Con map[1f] = maximum.formatPower() - for (cluster in chart.asIterable().clusterize(random)) { + for (cluster in chart.asIterable().clusterize(random, zeroBound = true)) { val perc = (cluster.center / maximum).toFloat() if (map.keys.none { (it - perc).absoluteValue < 0.08f }) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Clustering.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Clustering.kt index a4faa615f..a13d846c0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Clustering.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Clustering.kt @@ -90,6 +90,7 @@ private class MutableCluster>(var center: V) { private fun > Iterable.clusterize( random: RandomGenerator, initialClusters: Int = 1, + zeroBound: Boolean = false, identity: V, plus: (V, V) -> V, minus: (V, V) -> V, @@ -126,8 +127,27 @@ private fun > Iterable.clusterize( cluster.values.add(wrapped) } + if (zeroBound) { + min = minOf(min, identity) + max = maxOf(max, identity) + } + if (min == max) { return listOf(Cluster.Impl(listOf(min), min)) + } else { + // if "clusters" already satisfy constraints... + clusters.forEach { it.calculateCenter(identity, plus, minus, divInt, abs) } + values.forEach { it.updateError(abs, minus) } + + val maxError = values.maxOf { it.error } + + if (!heuristics(min, max, maxError)) { + return clusters.stream() + .filter { it.values.isNotEmpty() } + .map { Cluster.Impl(it.values.map { it.value }, it.center) } + .sorted { o1, o2 -> o2.values.size.compareTo(o1.values.size) } // большие кластеры должны идти первыми + .toList() + } } while (true) { @@ -182,8 +202,8 @@ private fun > Iterable.clusterize( // TODO: could use some tweaking private val DECIMAL_ERROR_TOLERANCE = Decimal("0.02") -fun Iterable.clusterize(random: RandomGenerator, clusters: Int? = null): List> { - return clusterize(random, clusters ?: 1, Decimal.ZERO, Decimal::plus, Decimal::minus, Decimal::div, Decimal::absoluteValue) { min, max, error -> +fun Iterable.clusterize(random: RandomGenerator, clusters: Int? = null, zeroBound: Boolean = false): List> { + return clusterize(random, clusters ?: 1, zeroBound, Decimal.ZERO, Decimal::plus, Decimal::minus, Decimal::div, Decimal::absoluteValue) { min, max, error -> if (clusters != null) false else diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt index 9b83d0c7a..7999f0f88 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt @@ -456,7 +456,7 @@ private fun formatHistoryChart( val rand = java.util.Random() if (maxTransferred.isNotZero && transferredMult > 0.2f) { - for (cluster in widget.transferred.clusterize(rand)) { + for (cluster in widget.transferred.clusterize(rand, zeroBound = true)) { val perc = (cluster.center / maxTransferred).toFloat() * transferredMult if (labelNames.keys.none { (it - perc).absoluteValue < 0.08f }) @@ -465,7 +465,7 @@ private fun formatHistoryChart( } if (maxReceived.isNotZero && receivedMult > 0.2f) { - for (cluster in widget.received.clusterize(rand)) { + for (cluster in widget.received.clusterize(rand, zeroBound = true)) { val perc = zero + (cluster.center / maxReceived).toFloat() * receivedMult if (labelNames.keys.none { (it - perc).absoluteValue < 0.08f }) @@ -473,7 +473,7 @@ private fun formatHistoryChart( } } - val clusters = diff.asIterable().clusterize(rand) + val clusters = diff.asIterable().clusterize(rand, zeroBound = true) for (cluster in clusters) { val perc: Float