From c5a2a57822dbc17d9ea7572eff35a9281bd53fd0 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Wed, 10 Jun 2026 23:42:12 -0400 Subject: [PATCH] SMID --- core/build.gradle | 5 + core/plugins/Iris/cache/instance | 2 +- core/src/main/java/art/arcane/iris/Iris.java | 2 + .../art/arcane/iris/core/IrisSettings.java | 1 + .../actuator/IrisTerrainNormalActuator.java | 5 +- .../components/MantleCarvingComponent.java | 13 +- .../modifier/IrisPerfectionModifier.java | 59 +++++++++ .../iris/engine/object/IrisDimension.java | 2 + .../context/ChunkedDoubleDataCache.java | 5 +- .../iris/util/simd/ScalarSimdKernels.java | 37 ++++++ .../arcane/iris/util/simd/SimdKernels.java | 11 ++ .../arcane/iris/util/simd/SimdSupport.java | 64 ++++++++++ .../iris/util/simd/VectorSimdKernels.java | 74 +++++++++++ .../iris/util/simd/SimdKernelsTest.java | 119 ++++++++++++++++++ 14 files changed, 385 insertions(+), 14 deletions(-) create mode 100644 core/src/main/java/art/arcane/iris/util/simd/ScalarSimdKernels.java create mode 100644 core/src/main/java/art/arcane/iris/util/simd/SimdKernels.java create mode 100644 core/src/main/java/art/arcane/iris/util/simd/SimdSupport.java create mode 100644 core/src/main/java/art/arcane/iris/util/simd/VectorSimdKernels.java create mode 100644 core/src/test/java/art/arcane/iris/util/simd/SimdKernelsTest.java diff --git a/core/build.gradle b/core/build.gradle index 038122f80..bed15cb73 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -214,10 +214,15 @@ tasks.named('compileJava', JavaCompile).configure { * We need parameter meta for the decree command system */ options.compilerArgs.add('-parameters') + options.compilerArgs.addAll(['--add-modules', 'jdk.incubator.vector']) options.encoding = 'UTF-8' options.debugOptions.debugLevel = 'none' } +tasks.named('test', Test).configure { + jvmArgs('--add-modules', 'jdk.incubator.vector') +} + tasks.named('processResources').configure { /** * Expand properties into plugin yml diff --git a/core/plugins/Iris/cache/instance b/core/plugins/Iris/cache/instance index 6f4c51402..33de9dec8 100644 --- a/core/plugins/Iris/cache/instance +++ b/core/plugins/Iris/cache/instance @@ -1 +1 @@ -748295809 \ No newline at end of file +983895216 \ No newline at end of file diff --git a/core/src/main/java/art/arcane/iris/Iris.java b/core/src/main/java/art/arcane/iris/Iris.java index b5eec567f..6859f10fa 100644 --- a/core/src/main/java/art/arcane/iris/Iris.java +++ b/core/src/main/java/art/arcane/iris/Iris.java @@ -70,6 +70,7 @@ import art.arcane.iris.util.common.plugin.VolmitSender; import art.arcane.iris.util.common.plugin.chunk.ChunkTickets; import art.arcane.iris.util.common.scheduling.J; import art.arcane.iris.util.common.misc.ServerProperties; +import art.arcane.iris.util.simd.SimdSupport; import art.arcane.volmlib.util.scheduling.Queue; import art.arcane.volmlib.util.scheduling.ShurikenQueue; import io.papermc.lib.PaperLib; @@ -547,6 +548,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware { private void enable() { PaperLibBootstrap.install(); + SimdSupport.install(); services = new KMap<>(); setupAudience(); Bindings.setupSentry(); diff --git a/core/src/main/java/art/arcane/iris/core/IrisSettings.java b/core/src/main/java/art/arcane/iris/core/IrisSettings.java index 194d4d686..edbc316a6 100644 --- a/core/src/main/java/art/arcane/iris/core/IrisSettings.java +++ b/core/src/main/java/art/arcane/iris/core/IrisSettings.java @@ -193,6 +193,7 @@ public class IrisSettings { public int objectLoaderCacheSize = 4_096; public int tectonicPlateSize = -1; public int mantleCleanupDelay = 200; + public boolean simdKernels = true; public int getTectonicPlateSize() { if (tectonicPlateSize > 0) diff --git a/core/src/main/java/art/arcane/iris/engine/actuator/IrisTerrainNormalActuator.java b/core/src/main/java/art/arcane/iris/engine/actuator/IrisTerrainNormalActuator.java index 9c11c6d55..0b70ebdf2 100644 --- a/core/src/main/java/art/arcane/iris/engine/actuator/IrisTerrainNormalActuator.java +++ b/core/src/main/java/art/arcane/iris/engine/actuator/IrisTerrainNormalActuator.java @@ -215,6 +215,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator int fluidHeight = dimension.getFluidHeight(); int clampedFluidHeight = Math.min(chunkHeight, fluidHeight); boolean bedrockEnabled = dimension.isBedrock(); + boolean hideOres = dimension.isHideOresForHiddenOre(); ChunkedDataCache biomeCache = context.getBiome(); ChunkedDataCache regionCache = context.getRegion(); ChunkedDataCache fluidCache = context.getFluid(); @@ -235,8 +236,8 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator int topY = Math.min(hf, chunkHeight - 1); BlockData fluid = fluidCache.get(xf, zf); BlockData rock = rockCache.get(xf, zf); - boolean hasSurfaceOres = biome.hasSurfaceOres() || region.hasSurfaceOres() || dimension.hasSurfaceOres(); - boolean hasUndergroundOres = biome.hasUndergroundOres() || region.hasUndergroundOres() || dimension.hasUndergroundOres(); + boolean hasSurfaceOres = !hideOres && (biome.hasSurfaceOres() || region.hasSurfaceOres() || dimension.hasSurfaceOres()); + boolean hasUndergroundOres = !hideOres && (biome.hasUndergroundOres() || region.hasUndergroundOres() || dimension.hasUndergroundOres()); KList blocks = null; KList fblocks = null; diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleCarvingComponent.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleCarvingComponent.java index 5804242b9..3d55d1056 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleCarvingComponent.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleCarvingComponent.java @@ -34,6 +34,8 @@ import art.arcane.iris.engine.object.IrisRange; import art.arcane.iris.util.project.context.ChunkContext; import art.arcane.iris.util.project.stream.ProceduralStream; import art.arcane.iris.util.project.stream.utility.ChunkFillableDoubleStream2D; +import art.arcane.iris.util.simd.SimdKernels; +import art.arcane.iris.util.simd.SimdSupport; import art.arcane.volmlib.util.documentation.ChunkCoordinates; import art.arcane.volmlib.util.mantle.flag.ReservedFlag; import art.arcane.volmlib.util.math.PowerOfTwoCoordinates; @@ -230,14 +232,9 @@ public class MantleCarvingComponent extends IrisMantleComponent { continue; } - double totalWeight = 0D; - double maxWeight = 0D; - for (double weight : weights) { - totalWeight += weight; - if (weight > maxWeight) { - maxWeight = weight; - } - } + SimdKernels kernels = SimdSupport.kernels(); + double totalWeight = kernels.sum(weights, weights.length); + double maxWeight = kernels.max(weights, weights.length); if (maxWeight < MIN_WEIGHT) { continue; diff --git a/core/src/main/java/art/arcane/iris/engine/modifier/IrisPerfectionModifier.java b/core/src/main/java/art/arcane/iris/engine/modifier/IrisPerfectionModifier.java index 96476b400..f2fbadcda 100644 --- a/core/src/main/java/art/arcane/iris/engine/modifier/IrisPerfectionModifier.java +++ b/core/src/main/java/art/arcane/iris/engine/modifier/IrisPerfectionModifier.java @@ -25,25 +25,61 @@ import art.arcane.iris.util.common.data.B; import art.arcane.iris.util.project.hunk.Hunk; import art.arcane.iris.util.common.parallel.BurstExecutor; import art.arcane.volmlib.util.scheduling.PrecisionStopwatch; +import org.bukkit.Material; import org.bukkit.block.data.Bisected; import org.bukkit.block.data.BlockData; import java.util.ArrayList; +import java.util.EnumMap; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; public class IrisPerfectionModifier extends EngineAssignedModifier { private static final BlockData AIR = B.get("AIR"); private static final BlockData WATER = B.get("WATER"); + private static final Map ORE_BASES = buildOreBases(); public IrisPerfectionModifier(Engine engine) { super(engine, "Perfection"); } + private static Map buildOreBases() { + Map map = new EnumMap<>(Material.class); + BlockData stone = B.get("STONE"); + BlockData deepslate = B.get("DEEPSLATE"); + BlockData netherrack = B.get("NETHERRACK"); + BlockData blackstone = B.get("BLACKSTONE"); + map.put(Material.COAL_ORE, stone); + map.put(Material.COPPER_ORE, stone); + map.put(Material.IRON_ORE, stone); + map.put(Material.GOLD_ORE, stone); + map.put(Material.REDSTONE_ORE, stone); + map.put(Material.LAPIS_ORE, stone); + map.put(Material.DIAMOND_ORE, stone); + map.put(Material.EMERALD_ORE, stone); + map.put(Material.DEEPSLATE_COAL_ORE, deepslate); + map.put(Material.DEEPSLATE_COPPER_ORE, deepslate); + map.put(Material.DEEPSLATE_IRON_ORE, deepslate); + map.put(Material.DEEPSLATE_GOLD_ORE, deepslate); + map.put(Material.DEEPSLATE_REDSTONE_ORE, deepslate); + map.put(Material.DEEPSLATE_LAPIS_ORE, deepslate); + map.put(Material.DEEPSLATE_DIAMOND_ORE, deepslate); + map.put(Material.DEEPSLATE_EMERALD_ORE, deepslate); + map.put(Material.NETHER_GOLD_ORE, netherrack); + map.put(Material.NETHER_QUARTZ_ORE, netherrack); + map.put(Material.ANCIENT_DEBRIS, netherrack); + map.put(Material.GILDED_BLACKSTONE, blackstone); + return map; + } + @Override public void onModify(int x, int z, Hunk output, boolean multicore, ChunkContext context) { PrecisionStopwatch p = PrecisionStopwatch.start(); + if (getDimension().isHideOresForHiddenOre()) { + hideOres(output, multicore); + } AtomicBoolean changed = new AtomicBoolean(true); int passes = 0; AtomicInteger changes = new AtomicInteger(); @@ -123,6 +159,29 @@ public class IrisPerfectionModifier extends EngineAssignedModifier { getEngine().getMetrics().getPerfection().put(p.getMilliseconds()); } + private void hideOres(Hunk output, boolean multicore) { + BurstExecutor burst = burst().burst(multicore); + int height = output.getHeight(); + for (int i = 0; i < 16; i++) { + int finalI = i; + burst.queue(() -> { + for (int j = 0; j < 16; j++) { + for (int k = height - 1; k >= 0; k--) { + BlockData block = output.get(finalI, k, j); + if (block == null) { + continue; + } + BlockData base = ORE_BASES.get(block.getMaterial()); + if (base != null) { + output.set(finalI, k, j, base); + } + } + } + }); + } + burst.complete(); + } + private int getHeight(Hunk output, int x, int z) { for (int i = output.getHeight() - 1; i >= 0; i--) { BlockData b = output.get(x, i, z); diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java index e205316c1..d6ad1664d 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java @@ -140,6 +140,8 @@ public class IrisDimension extends IrisRegistrant { private boolean debugSmartBore = false; @Desc("Generate decorations or not") private boolean decorate = true; + @Desc("When true, every ore block the generator would place (terrain ores, deposits, ores inside objects) is replaced with its base material such as stone, deepslate or netherrack, so drop-control plugins like HiddenOre can manage ore rewards themselves") + private boolean hideOresForHiddenOre = false; @Desc("Use post processing or not") private boolean postProcessing = true; @Desc("Add slabs in post processing") diff --git a/core/src/main/java/art/arcane/iris/util/project/context/ChunkedDoubleDataCache.java b/core/src/main/java/art/arcane/iris/util/project/context/ChunkedDoubleDataCache.java index 2826704f6..2f5a2c9a5 100644 --- a/core/src/main/java/art/arcane/iris/util/project/context/ChunkedDoubleDataCache.java +++ b/core/src/main/java/art/arcane/iris/util/project/context/ChunkedDoubleDataCache.java @@ -2,6 +2,7 @@ package art.arcane.iris.util.project.context; import art.arcane.iris.util.project.stream.ProceduralStream; import art.arcane.iris.util.project.stream.utility.ChunkFillableDoubleStream2D; +import art.arcane.iris.util.simd.SimdSupport; import art.arcane.volmlib.util.documentation.BlockCoordinates; import java.util.Arrays; @@ -56,9 +57,7 @@ public class ChunkedDoubleDataCache { if (stream instanceof ChunkFillableDoubleStream2D cachedStream) { cachedStream.fillChunkDoubles(x, z, data); if (roundedTarget != null) { - for (int index = 0; index < 256; index++) { - roundedTarget[index] = (int) Math.round(data[index]); - } + SimdSupport.kernels().roundToInt(data, roundedTarget, 256); } return; } diff --git a/core/src/main/java/art/arcane/iris/util/simd/ScalarSimdKernels.java b/core/src/main/java/art/arcane/iris/util/simd/ScalarSimdKernels.java new file mode 100644 index 000000000..85a4ef275 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/simd/ScalarSimdKernels.java @@ -0,0 +1,37 @@ +package art.arcane.iris.util.simd; + +public final class ScalarSimdKernels implements SimdKernels { + @Override + public String describe() { + return "scalar"; + } + + @Override + public void roundToInt(double[] source, int[] target, int length) { + for (int index = 0; index < length; index++) { + target[index] = (int) Math.round(source[index]); + } + } + + @Override + public double sum(double[] values, int length) { + double total = 0D; + for (int index = 0; index < length; index++) { + total += values[index]; + } + + return total; + } + + @Override + public double max(double[] values, int length) { + double best = Double.NEGATIVE_INFINITY; + for (int index = 0; index < length; index++) { + if (values[index] > best) { + best = values[index]; + } + } + + return best; + } +} diff --git a/core/src/main/java/art/arcane/iris/util/simd/SimdKernels.java b/core/src/main/java/art/arcane/iris/util/simd/SimdKernels.java new file mode 100644 index 000000000..243dd59ee --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/simd/SimdKernels.java @@ -0,0 +1,11 @@ +package art.arcane.iris.util.simd; + +public interface SimdKernels { + String describe(); + + void roundToInt(double[] source, int[] target, int length); + + double sum(double[] values, int length); + + double max(double[] values, int length); +} diff --git a/core/src/main/java/art/arcane/iris/util/simd/SimdSupport.java b/core/src/main/java/art/arcane/iris/util/simd/SimdSupport.java new file mode 100644 index 000000000..37aa34cdc --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/simd/SimdSupport.java @@ -0,0 +1,64 @@ +package art.arcane.iris.util.simd; + +import art.arcane.iris.Iris; +import art.arcane.iris.core.IrisSettings; + +public final class SimdSupport { + private static final String VECTOR_MODULE = "jdk.incubator.vector"; + private static final boolean MODULE_PRESENT = ModuleLayer.boot().findModule(VECTOR_MODULE).isPresent(); + private static final SimdKernels KERNELS = selectKernels(); + + private SimdSupport() { + } + + public static SimdKernels kernels() { + return KERNELS; + } + + public static boolean isVectorModulePresent() { + return MODULE_PRESENT; + } + + public static boolean isVectorized() { + return !(KERNELS instanceof ScalarSimdKernels); + } + + public static void install() { + if (isVectorized()) { + Iris.info("SIMD: vector kernels enabled (" + KERNELS.describe() + ")"); + } else if (!MODULE_PRESENT) { + Iris.info("SIMD: scalar kernels active; add --add-modules " + VECTOR_MODULE + " to JVM flags to enable vectorized generation kernels"); + } else { + Iris.info("SIMD: vector kernels disabled (performance.simdKernels=false)"); + } + } + + public static SimdKernels createVectorKernels() { + if (!MODULE_PRESENT) { + return null; + } + + try { + return (SimdKernels) Class.forName("art.arcane.iris.util.simd.VectorSimdKernels").getDeclaredConstructor().newInstance(); + } catch (Throwable e) { + return null; + } + } + + private static SimdKernels selectKernels() { + if (!simdEnabledInSettings()) { + return new ScalarSimdKernels(); + } + + SimdKernels vector = createVectorKernels(); + return vector == null ? new ScalarSimdKernels() : vector; + } + + private static boolean simdEnabledInSettings() { + try { + return IrisSettings.get().getPerformance().isSimdKernels(); + } catch (Throwable e) { + return true; + } + } +} diff --git a/core/src/main/java/art/arcane/iris/util/simd/VectorSimdKernels.java b/core/src/main/java/art/arcane/iris/util/simd/VectorSimdKernels.java new file mode 100644 index 000000000..0b9d73bcf --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/simd/VectorSimdKernels.java @@ -0,0 +1,74 @@ +package art.arcane.iris.util.simd; + +import jdk.incubator.vector.DoubleVector; +import jdk.incubator.vector.IntVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorShape; +import jdk.incubator.vector.VectorSpecies; + +public final class VectorSimdKernels implements SimdKernels { + private static final VectorSpecies DOUBLE_SPECIES = DoubleVector.SPECIES_PREFERRED; + private static final VectorSpecies INT_SPECIES = VectorSpecies.of(int.class, VectorShape.forBitSize(DOUBLE_SPECIES.length() * Integer.SIZE)); + + @Override + public String describe() { + return DOUBLE_SPECIES.length() + "x64-bit lanes, " + DOUBLE_SPECIES.vectorShape(); + } + + @Override + public void roundToInt(double[] source, int[] target, int length) { + int lanes = DOUBLE_SPECIES.length(); + int bound = DOUBLE_SPECIES.loopBound(length); + int index = 0; + for (; index < bound; index += lanes) { + DoubleVector shifted = DoubleVector.fromArray(DOUBLE_SPECIES, source, index).add(0.5D); + IntVector truncated = (IntVector) shifted.convertShape(VectorOperators.D2I, INT_SPECIES, 0); + DoubleVector truncatedBack = (DoubleVector) truncated.convertShape(VectorOperators.I2D, DOUBLE_SPECIES, 0); + VectorMask needsDecrement = shifted.lt(truncatedBack).cast(INT_SPECIES); + truncated.sub(1, needsDecrement).intoArray(target, index); + } + + for (; index < length; index++) { + target[index] = (int) Math.round(source[index]); + } + } + + @Override + public double sum(double[] values, int length) { + int lanes = DOUBLE_SPECIES.length(); + int bound = DOUBLE_SPECIES.loopBound(length); + DoubleVector accumulator = DoubleVector.zero(DOUBLE_SPECIES); + int index = 0; + for (; index < bound; index += lanes) { + accumulator = accumulator.add(DoubleVector.fromArray(DOUBLE_SPECIES, values, index)); + } + + double total = accumulator.reduceLanes(VectorOperators.ADD); + for (; index < length; index++) { + total += values[index]; + } + + return total; + } + + @Override + public double max(double[] values, int length) { + int lanes = DOUBLE_SPECIES.length(); + int bound = DOUBLE_SPECIES.loopBound(length); + DoubleVector accumulator = DoubleVector.broadcast(DOUBLE_SPECIES, Double.NEGATIVE_INFINITY); + int index = 0; + for (; index < bound; index += lanes) { + accumulator = accumulator.max(DoubleVector.fromArray(DOUBLE_SPECIES, values, index)); + } + + double best = accumulator.reduceLanes(VectorOperators.MAX); + for (; index < length; index++) { + if (values[index] > best) { + best = values[index]; + } + } + + return best; + } +} diff --git a/core/src/test/java/art/arcane/iris/util/simd/SimdKernelsTest.java b/core/src/test/java/art/arcane/iris/util/simd/SimdKernelsTest.java new file mode 100644 index 000000000..e0751e29a --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/simd/SimdKernelsTest.java @@ -0,0 +1,119 @@ +package art.arcane.iris.util.simd; + +import org.junit.Assume; +import org.junit.Test; + +import java.util.Random; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class SimdKernelsTest { + private static final double[] BOUNDARY_VALUES = { + 0D, 0.5D, -0.5D, 1.5D, -1.5D, 2.5D, -2.5D, 63.5D, -63.5D, 64.49999D, -64.49999D, + 0.4999999D, -0.4999999D, 255.5D, -255.5D, 319.999D, -64D, 2031.5D, -2031.5D, 0.0001D, -0.0001D + }; + + private static double[] terrainLikeValues(int length) { + Random random = new Random(42L); + double[] values = new double[length]; + for (int index = 0; index < length; index++) { + values[index] = (random.nextDouble() - 0.5D) * 8192D; + } + + return values; + } + + @Test + public void scalarRoundToIntMatchesMathRound() { + ScalarSimdKernels kernels = new ScalarSimdKernels(); + int[] rounded = new int[BOUNDARY_VALUES.length]; + kernels.roundToInt(BOUNDARY_VALUES, rounded, BOUNDARY_VALUES.length); + for (int index = 0; index < BOUNDARY_VALUES.length; index++) { + assertEquals("index " + index + " value " + BOUNDARY_VALUES[index], (int) Math.round(BOUNDARY_VALUES[index]), rounded[index]); + } + } + + @Test + public void scalarSumAndMaxMatchManualLoop() { + ScalarSimdKernels kernels = new ScalarSimdKernels(); + double[] values = terrainLikeValues(256); + double expectedSum = 0D; + double expectedMax = Double.NEGATIVE_INFINITY; + for (double value : values) { + expectedSum += value; + if (value > expectedMax) { + expectedMax = value; + } + } + + assertEquals(expectedSum, kernels.sum(values, values.length), 0D); + assertEquals(expectedMax, kernels.max(values, values.length), 0D); + } + + @Test + public void vectorRoundToIntMatchesScalarOnBoundaries() { + SimdKernels vector = requireVectorKernels(); + ScalarSimdKernels scalar = new ScalarSimdKernels(); + int[] vectorRounded = new int[BOUNDARY_VALUES.length]; + int[] scalarRounded = new int[BOUNDARY_VALUES.length]; + vector.roundToInt(BOUNDARY_VALUES, vectorRounded, BOUNDARY_VALUES.length); + scalar.roundToInt(BOUNDARY_VALUES, scalarRounded, BOUNDARY_VALUES.length); + for (int index = 0; index < BOUNDARY_VALUES.length; index++) { + assertEquals("index " + index + " value " + BOUNDARY_VALUES[index], scalarRounded[index], vectorRounded[index]); + } + } + + @Test + public void vectorRoundToIntMatchesScalarOnTerrainSweep() { + SimdKernels vector = requireVectorKernels(); + ScalarSimdKernels scalar = new ScalarSimdKernels(); + for (int length : new int[]{256, 251, 1, 7}) { + double[] values = terrainLikeValues(length); + int[] vectorRounded = new int[length]; + int[] scalarRounded = new int[length]; + vector.roundToInt(values, vectorRounded, length); + scalar.roundToInt(values, scalarRounded, length); + for (int index = 0; index < length; index++) { + assertEquals("length " + length + " index " + index + " value " + values[index], scalarRounded[index], vectorRounded[index]); + } + } + } + + @Test + public void vectorSumMatchesScalarWithinTolerance() { + SimdKernels vector = requireVectorKernels(); + ScalarSimdKernels scalar = new ScalarSimdKernels(); + for (int length : new int[]{256, 251, 1, 7}) { + double[] values = terrainLikeValues(length); + double expected = scalar.sum(values, length); + double actual = vector.sum(values, length); + assertEquals("length " + length, expected, actual, Math.max(1e-9D, Math.abs(expected) * 1e-12D)); + } + } + + @Test + public void vectorMaxMatchesScalarExactly() { + SimdKernels vector = requireVectorKernels(); + ScalarSimdKernels scalar = new ScalarSimdKernels(); + for (int length : new int[]{256, 251, 1, 7}) { + double[] values = terrainLikeValues(length); + assertEquals("length " + length, scalar.max(values, length), vector.max(values, length), 0D); + } + } + + @Test + public void vectorMaxOfAllZeroWeightsIsZero() { + SimdKernels vector = requireVectorKernels(); + double[] weights = new double[256]; + assertEquals(0D, vector.max(weights, weights.length), 0D); + assertEquals(0D, vector.sum(weights, weights.length), 0D); + } + + private static SimdKernels requireVectorKernels() { + Assume.assumeTrue("jdk.incubator.vector module not present in test JVM", SimdSupport.isVectorModulePresent()); + SimdKernels vector = SimdSupport.createVectorKernels(); + assertNotNull("vector kernels failed to instantiate despite module being present", vector); + return vector; + } +}