Commit 2c9eea60 authored by Numanoglu's avatar Numanoglu
Browse files

Refine AABB BVH heuristic exploration

parent 35a777b9
......@@ -4,8 +4,8 @@ import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.SplitStrategy;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.SplitStrategy;
/**
* Central policy for cheap BVH decisions in geometry checks.
......@@ -250,7 +250,7 @@ public final class BvhUsagePolicy {
public static boolean shouldUseTree(BvhCheckType checkType, BvhInputSummary summary) {
int elementCount = summary.getElementCount();
if (elementCount < 2) {
if (elementCount < 20) {
return false;
}
if (elementCount < thresholdFor(checkType)) {
......
......@@ -49,8 +49,8 @@ import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.edge.EdgePolygon;
import de.hft.stuttgart.citydoctor2.edge.IntersectPlanarPolygons;
import de.hft.stuttgart.citydoctor2.edge.MeshSurface;
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.core;
import de.hft.stuttgart.citydoctor2.checks.aabb.fixtures.SyntheticCityGmlLikeGeometryFactory;
import de.hft.stuttgart.citydoctor2.checks.aabb.fixtures.SyntheticRingGeometryFactory;
import de.hft.stuttgart.citydoctor2.checks.aabb.fixtures.SyntheticSolidGeometryFactory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
......@@ -15,9 +19,17 @@ import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.SplitStrategy;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.SplitStrategy;
/**
* Covers the current tree-usage policy with small, readable synthetic inputs.
*
* These tests are not meant to prove an optimal heuristic. They guard the
* conservative decisions that are currently wired into BvhUsagePolicy.
*
* @author Numanoglu
*/
public class BvhUsagePolicyTest {
@Test
......@@ -71,10 +83,12 @@ public class BvhUsagePolicyTest {
}
private static BvhUsagePolicy.BvhInputSummary summaryForPolygons(Geometry geometry) {
// Production code computes the same cheap summary from each element's AABB.
return BvhUsagePolicy.BvhInputSummary.ofElements(geometry.getPolygons(), polygon -> AABB.of(polygon.getOriginal()));
}
private static List<AABB> edgeBoxes(Geometry geometry) {
// Ring self-intersection uses segment boxes rather than whole polygon boxes.
List<AABB> boxes = new ArrayList<>();
for (Polygon polygon : geometry.getPolygons()) {
LinearRing ring = polygon.getExteriorRing();
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.core;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import java.util.Arrays;
import java.util.List;
//import de.hft.stuttgart.citydoctor2.checks.geometry.*;
//import de.hft.stuttgart.citydoctor2.checks.aabb.*; //<-evtl *geomCheks hier verlagern
import de.hft.stuttgart.citydoctor2.datastructure.*;
import de.hft.stuttgart.citydoctor2.checks.aabb.NestedRingsCheckAABB;
import de.hft.stuttgart.citydoctor2.checks.aabb.RingSelfIntCheckAABB;
import de.hft.stuttgart.citydoctor2.checks.aabb.SolidSelfIntCheckAABB;
/**
* Manual test runner for basic geometry checks with AABB involvement.
* Manual smoke runner for the older AABB check classes.
*
* The class is intentionally simple and prints its result to the console. It is
* useful when the checks are inspected locally, while systematic assertions live
* in the dedicated correctness tests.
*
* @author Numanoglu
*/
public class GeometryChecksWithAABBTest {
public static void main(String[] args) {
System.out.println("=== Geometry Checks with AABB – Manual Test Runner ===");
System.out.println("=== Geometry Checks with AABB - Manual Test Runner ===");
//testTooFewPoints();
testNestedRings();
......@@ -95,7 +103,7 @@ public class GeometryChecksWithAABBTest {
ConcretePolygon p = new ConcretePolygon();
p.setExteriorRing(ring);
// TODO Check semantics of Orientation
// Orientation is not the focus here; the geometry mainly provides parent links.
Geometry g = new Geometry(GeometryType.SOLID, Lod.LOD2, Geometry.Orientation.OUTWARD);
g.addPolygon(p); // ensures parent links exist
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.correctness;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.correctness;
import de.hft.stuttgart.citydoctor2.checks.aabb.fixtures.SyntheticNestedRingGeometryFactory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.correctness;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.correctness;
import de.hft.stuttgart.citydoctor2.checks.aabb.fixtures.SyntheticRingGeometryFactory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.correctness;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
......@@ -18,8 +18,8 @@ import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.exceptions.CityDoctorWriteException;
import de.hft.stuttgart.citydoctor2.checks.util.GeometryTestUtils;
import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil;
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.correctness;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
......@@ -19,8 +19,8 @@ import de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.correctness;
import de.hft.stuttgart.citydoctor2.checks.aabb.fixtures.SyntheticSolidGeometryFactory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
......@@ -14,9 +16,9 @@ import org.junit.jupiter.api.Test;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.datastructure.bht.SplitStrategy;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.SplitStrategy;
import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.correctness;
import static org.junit.Assert.*;
......@@ -18,8 +18,8 @@ import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree;
/*
* First comparison SolidSelfIntersection version with Bounding Volume Tree vs old version
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.fixtures;
import java.util.List;
......@@ -15,14 +15,14 @@ import de.hft.stuttgart.citydoctor2.datastructure.Lod;
*
* @author Numanoglu
*/
final class BvhSyntheticScenarioCatalog {
public final class BvhSyntheticScenarioCatalog {
private static final double EPSILON = 0.001;
private BvhSyntheticScenarioCatalog() {
}
static List<SsiScenario> ssiScenarios() {
public static List<SsiScenario> ssiScenarios() {
return List.of(
new SsiScenario("sep-grid-small",
SyntheticSolidGeometryFactory.separatedBoxGrid(Lod.LOD2, 6, 6)),
......@@ -74,7 +74,7 @@ final class BvhSyntheticScenarioCatalog {
SyntheticCityGmlLikeGeometryFactory.variedRoofDistrict(Lod.LOD2, 240)));
}
static List<NestedScenario> nestedScenarios() {
public static List<NestedScenario> nestedScenarios() {
return List.of(
new NestedScenario("disjoint-small",
SyntheticNestedRingGeometryFactory.manyDisjointInnerRings(120)),
......@@ -108,7 +108,7 @@ final class BvhSyntheticScenarioCatalog {
SyntheticNestedRingGeometryFactory.spreadSweepInnerRings(240, 20.0)));
}
static List<RsiScenario> rsiScenarios() {
public static List<RsiScenario> rsiScenarios() {
return List.of(
new RsiScenario("rsi-small",
SyntheticRingGeometryFactory.performanceRingGeometry(200, 100, EPSILON)),
......@@ -132,9 +132,9 @@ final class BvhSyntheticScenarioCatalog {
SyntheticRingGeometryFactory.spreadSweepGeometry(240, 30.0)));
}
static final class SsiScenario {
final String name;
final Geometry geometry;
public static final class SsiScenario {
public final String name;
public final Geometry geometry;
SsiScenario(String name, Geometry geometry) {
this.name = name;
......@@ -142,9 +142,9 @@ final class BvhSyntheticScenarioCatalog {
}
}
static final class NestedScenario {
final String name;
final ConcretePolygon polygon;
public static final class NestedScenario {
public final String name;
public final ConcretePolygon polygon;
NestedScenario(String name, ConcretePolygon polygon) {
this.name = name;
......@@ -152,9 +152,9 @@ final class BvhSyntheticScenarioCatalog {
}
}
static final class RsiScenario {
final String name;
final Geometry geometry;
public static final class RsiScenario {
public final String name;
public final Geometry geometry;
RsiScenario(String name, Geometry geometry) {
this.name = name;
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.fixtures;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
......@@ -19,7 +19,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
*
* @author Numanoglu
*/
final class SyntheticCityGmlLikeGeometryFactory {
public final class SyntheticCityGmlLikeGeometryFactory {
private SyntheticCityGmlLikeGeometryFactory() {
}
......@@ -28,7 +28,7 @@ final class SyntheticCityGmlLikeGeometryFactory {
* Creates several blocks with mixed compact, elongated, and roof-like buildings.
* Useful as a balanced city-district fixture with moderate spatial separation.
*/
static Geometry mixedUrbanDistrict(Lod lod, int blockColumns, int blockRows) {
public static Geometry mixedUrbanDistrict(Lod lod, int blockColumns, int blockRows) {
Geometry geometry = newSolid(lod);
for (int blockX = 0; blockX < blockColumns; blockX++) {
for (int blockY = 0; blockY < blockRows; blockY++) {
......@@ -45,7 +45,7 @@ final class SyntheticCityGmlLikeGeometryFactory {
* Creates block-perimeter buildings around open courtyards. This produces many
* nearby but not necessarily intersecting AABBs, closer to dense urban blocks.
*/
static Geometry courtyardDistrict(Lod lod, int blockCount) {
public static Geometry courtyardDistrict(Lod lod, int blockCount) {
Geometry geometry = newSolid(lod);
for (int block = 0; block < blockCount; block++) {
double originX = (block % 4) * 62.0;
......@@ -64,7 +64,7 @@ final class SyntheticCityGmlLikeGeometryFactory {
* Creates many small buildings with alternating flat and gabled roof shapes.
* Useful for a larger polygon count with city-like height and footprint jitter.
*/
static Geometry variedRoofDistrict(Lod lod, int buildingCount) {
public static Geometry variedRoofDistrict(Lod lod, int buildingCount) {
Geometry geometry = newSolid(lod);
for (int i = 0; i < buildingCount; i++) {
double x = (i % 12) * 13.0 + ((i / 12) % 2) * 4.0;
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.fixtures;
import java.io.File;
import java.util.ArrayList;
......@@ -56,12 +56,12 @@ import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
* @author Numanoglu
*/
final class SyntheticNestedRingGeometryFactory {
public final class SyntheticNestedRingGeometryFactory {
private SyntheticNestedRingGeometryFactory() {
}
static Geometry complexNestedRingGeometry() {
public static Geometry complexNestedRingGeometry() {
Geometry geometry = new Geometry(GeometryType.SOLID, Lod.LOD2, Orientation.OUTWARD);
for (ConcretePolygon polygon : complexNestedRingPolygons()) {
geometry.addPolygon(polygon);
......@@ -70,7 +70,7 @@ final class SyntheticNestedRingGeometryFactory {
return geometry;
}
static List<ConcretePolygon> complexNestedRingPolygons() {
public static List<ConcretePolygon> complexNestedRingPolygons() {
List<ConcretePolygon> polygons = new ArrayList<>();
polygons.add(manyDisjointInnerRings(100));
polygons.add(oneNestedPairAmongMany(96));
......@@ -81,7 +81,7 @@ final class SyntheticNestedRingGeometryFactory {
return polygons;
}
static ConcretePolygon manyDisjointInnerRings(int count) {
public static ConcretePolygon manyDisjointInnerRings(int count) {
ConcretePolygon polygon = basePolygon(count);
for (int i = 0; i < count; i++) {
double x = gridX(i, 10) * 8.0 + 4.0;
......@@ -91,14 +91,14 @@ final class SyntheticNestedRingGeometryFactory {
return polygon;
}
static ConcretePolygon oneNestedPairAmongMany(int count) {
public static ConcretePolygon oneNestedPairAmongMany(int count) {
ConcretePolygon polygon = manyDisjointInnerRings(Math.max(0, count - 2));
polygon.addInteriorRing(squareRing(5.0, 85.0, 5.0));
polygon.addInteriorRing(squareRing(5.0, 85.0, 1.0));
return polygon;
}
static ConcretePolygon concentricNestedRings(int count) {
public static ConcretePolygon concentricNestedRings(int count) {
ConcretePolygon polygon = basePolygon(count);
for (int i = 0; i < count; i++) {
double halfSize = Math.max(1.0, count - i);
......@@ -107,7 +107,7 @@ final class SyntheticNestedRingGeometryFactory {
return polygon;
}
static ConcretePolygon overlappingAabbsButNotNested(int count) {
public static ConcretePolygon overlappingAabbsButNotNested(int count) {
ConcretePolygon polygon = basePolygon(count);
for (int i = 0; i < count; i++) {
double x = 6.0 + (i % 12) * 6.0;
......@@ -118,7 +118,7 @@ final class SyntheticNestedRingGeometryFactory {
return polygon;
}
static ConcretePolygon clusteredInnerRings(int clusterCount, int ringsPerCluster) {
public static ConcretePolygon clusteredInnerRings(int clusterCount, int ringsPerCluster) {
ConcretePolygon polygon = basePolygon(clusterCount * ringsPerCluster);
for (int cluster = 0; cluster < clusterCount; cluster++) {
double clusterX = 8.0 + (cluster % 4) * 22.0;
......@@ -132,14 +132,14 @@ final class SyntheticNestedRingGeometryFactory {
return polygon;
}
static ConcretePolygon clusteredInnerRingsWithNestedPair(int clusterCount, int ringsPerCluster) {
public static ConcretePolygon clusteredInnerRingsWithNestedPair(int clusterCount, int ringsPerCluster) {
ConcretePolygon polygon = clusteredInnerRings(clusterCount, ringsPerCluster);
polygon.addInteriorRing(squareRing(88.0, 88.0, 5.0));
polygon.addInteriorRing(squareRing(88.0, 88.0, 1.0));
return polygon;
}
static ConcretePolygon aspectSweepInnerRings(int count, double aspectRatio) {
public static ConcretePolygon aspectSweepInnerRings(int count, double aspectRatio) {
int columns = (int) Math.ceil(Math.sqrt(count));
double width = Math.max(1.0, aspectRatio);
double extent = columns * (width + 4.0) + 20.0;
......@@ -152,7 +152,7 @@ final class SyntheticNestedRingGeometryFactory {
return polygon;
}
static ConcretePolygon spreadSweepInnerRings(int count, double spacing) {
public static ConcretePolygon spreadSweepInnerRings(int count, double spacing) {
int columns = (int) Math.ceil(Math.sqrt(count));
double extent = columns * spacing + 20.0;
ConcretePolygon polygon = basePolygonWithExtent(extent);
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.fixtures;
import java.io.File;
......@@ -42,12 +42,12 @@ import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
*
* @author Numanoglu
*/
final class SyntheticRingGeometryFactory {
public final class SyntheticRingGeometryFactory {
private SyntheticRingGeometryFactory() {
}
static Geometry correctnessRingGeometry(double epsilon) {
public static Geometry correctnessRingGeometry(double epsilon) {
Geometry geometry = new Geometry(GeometryType.SOLID, Lod.LOD2, Orientation.OUTWARD);
addRingPolygon(geometry, rectangle());
addRingPolygon(geometry, bowTie());
......@@ -58,7 +58,7 @@ final class SyntheticRingGeometryFactory {
return geometry;
}
static Geometry performanceRingGeometry(int convexVertexCount, int zigZagSegments, double epsilon) {
public static Geometry performanceRingGeometry(int convexVertexCount, int zigZagSegments, double epsilon) {
Geometry geometry = new Geometry(GeometryType.SOLID, Lod.LOD2, Orientation.OUTWARD);
addRingPolygon(geometry, largeConvexRing(convexVertexCount, 0.0, 0.0, 0.0));
addRingPolygon(geometry, zigZagCorridor(zigZagSegments));
......@@ -68,7 +68,7 @@ final class SyntheticRingGeometryFactory {
return geometry;
}
static Geometry aspectSweepGeometry(int ringCount, double aspectRatio) {
public static Geometry aspectSweepGeometry(int ringCount, double aspectRatio) {
Geometry geometry = new Geometry(GeometryType.SOLID, Lod.LOD2, Orientation.OUTWARD);
double width = Math.max(1.0, aspectRatio);
for (int i = 0; i < ringCount; i++) {
......@@ -80,7 +80,7 @@ final class SyntheticRingGeometryFactory {
return geometry;
}
static Geometry spreadSweepGeometry(int ringCount, double spacing) {
public static Geometry spreadSweepGeometry(int ringCount, double spacing) {
Geometry geometry = new Geometry(GeometryType.SOLID, Lod.LOD2, Orientation.OUTWARD);
for (int i = 0; i < ringCount; i++) {
double x = (i % 20) * spacing;
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.fixtures;
import java.io.File;
......@@ -30,12 +30,12 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
*
* @author Numanoglu
*/
final class SyntheticSolidGeometryFactory {
public final class SyntheticSolidGeometryFactory {
private SyntheticSolidGeometryFactory() {
}
static Geometry separatedBoxGrid(Lod lod, int xCount, int yCount) {
public static Geometry separatedBoxGrid(Lod lod, int xCount, int yCount) {
Geometry geometry = newSolid(lod);
double spacing = 6.0;
for (int ix = 0; ix < xCount; ix++) {
......@@ -47,7 +47,7 @@ final class SyntheticSolidGeometryFactory {
return geometry;
}
static Geometry overlappingBoxGrid(Lod lod, int xCount, int yCount) {
public static Geometry overlappingBoxGrid(Lod lod, int xCount, int yCount) {
Geometry geometry = separatedBoxGrid(lod, xCount, yCount);
addBox(geometry, 6.0, 6.0, 0.5, 6.0, 2.8, 3.5);
addBox(geometry, 8.0, 5.5, 0.0, 2.8, 6.0, 4.2);
......@@ -55,7 +55,7 @@ final class SyntheticSolidGeometryFactory {
return geometry;
}
static Geometry denseBoxClusters(Lod lod, int clusterCount, int boxesPerCluster) {
public static Geometry denseBoxClusters(Lod lod, int clusterCount, int boxesPerCluster) {
Geometry geometry = newSolid(lod);
double clusterSpacing = 30.0;
for (int cluster = 0; cluster < clusterCount; cluster++) {
......@@ -70,7 +70,7 @@ final class SyntheticSolidGeometryFactory {
return geometry;
}
static Geometry longThinSlabs(Lod lod, int count) {
public static Geometry longThinSlabs(Lod lod, int count) {
Geometry geometry = newSolid(lod);
for (int i = 0; i < count; i++) {
double y = i * 0.75;
......@@ -80,7 +80,7 @@ final class SyntheticSolidGeometryFactory {
return geometry;
}
static Geometry flatBoxGrid(Lod lod, int count) {
public static Geometry flatBoxGrid(Lod lod, int count) {
Geometry geometry = newSolid(lod);
for (int i = 0; i < count; i++) {
double x = (i % 10) * 5.0;
......@@ -91,7 +91,7 @@ final class SyntheticSolidGeometryFactory {
return geometry;
}
static Geometry thinRatioSweepBoxes(Lod lod, int count, double thinRatio) {
public static Geometry thinRatioSweepBoxes(Lod lod, int count, double thinRatio) {
Geometry geometry = newSolid(lod);
int columns = (int) Math.ceil(Math.sqrt(count));
int thinCount = (int) Math.round(count * thinRatio);
......@@ -108,7 +108,7 @@ final class SyntheticSolidGeometryFactory {
return geometry;
}
static Geometry aspectSweepBoxes(Lod lod, int count, double aspectRatio) {
public static Geometry aspectSweepBoxes(Lod lod, int count, double aspectRatio) {
Geometry geometry = newSolid(lod);
int columns = (int) Math.ceil(Math.sqrt(count));
double width = Math.max(1.0, aspectRatio);
......@@ -122,7 +122,7 @@ final class SyntheticSolidGeometryFactory {
return geometry;
}
static Geometry spreadSweepBoxes(Lod lod, int count, double spacing) {
public static Geometry spreadSweepBoxes(Lod lod, int count, double spacing) {
Geometry geometry = newSolid(lod);
int columns = (int) Math.ceil(Math.sqrt(count));
for (int i = 0; i < count; i++) {
......@@ -134,7 +134,7 @@ final class SyntheticSolidGeometryFactory {
return geometry;
}
static Geometry relativeVolumeSweepBoxes(Lod lod, int count, double boxSize, double spacingFactor) {
public static Geometry relativeVolumeSweepBoxes(Lod lod, int count, double boxSize, double spacingFactor) {
Geometry geometry = newSolid(lod);
int columns = (int) Math.ceil(Math.sqrt(count));
double spacing = boxSize * spacingFactor;
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.heuristics;
import de.hft.stuttgart.citydoctor2.checks.aabb.fixtures.BvhSyntheticScenarioCatalog;
import de.hft.stuttgart.citydoctor2.checks.aabb.support.BvhExplorationCsvWriter;
import de.hft.stuttgart.citydoctor2.checks.aabb.support.BvhHeuristicTimingSupport;
import de.hft.stuttgart.citydoctor2.checks.aabb.support.BvhInputMetricsCollector;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
......@@ -19,14 +26,18 @@ import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.datastructure.bht.SplitStrategy;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.SplitStrategy;
/**
* Collects compact timing observations and broad-phase metrics for BVH strategy
* selection from synthetic fixtures.
*
* The synthetic scenarios are the calibration side of the heuristic workflow:
* they deliberately vary shape and distribution properties so candidate rules
* can be proposed before they are checked against real CityGML data.
*
* @author Numanoglu
*/
@Tag("performance")
......@@ -34,19 +45,44 @@ public class BvhStrategyHeuristicExplorationTest {
private static final double DELTA = 0.001;
private static final double EPSILON = 0.001;
private static final Path OUTPUT_DIRECTORY = Path.of("target", "bvh-exploration");
@Test
public void exploreSsiNestedAndRsiStrategyCandidates() {
public void exploreSsiNestedAndRsiStrategyCandidates() throws IOException {
// Keep all three checks in one table so metric trends can be compared side by side.
List<BvhHeuristicTimingSupport.Observation> observations = new ArrayList<>();
addSsiObservations(observations);
addNestedObservations(observations);
addRsiObservations(observations);
printObservationSummary(observations);
printBucketSummary(observations);
List<BvhExplorationCsvWriter.BucketSummaryRecord> bucketSummary = printBucketSummary(observations);
BvhExplorationCsvWriter.writeObservations(
OUTPUT_DIRECTORY.resolve("synthetic_observations.csv"),
toSyntheticRecords(observations));
BvhExplorationCsvWriter.writeBucketSummary(
OUTPUT_DIRECTORY.resolve("synthetic_bucket_summary.csv"),
bucketSummary);
}
private static List<BvhExplorationCsvWriter.ObservationRecord> toSyntheticRecords(
List<BvhHeuristicTimingSupport.Observation> observations) {
// Synthetic observations are labelled as calibration data, not as final evaluation data.
List<BvhExplorationCsvWriter.ObservationRecord> records = new ArrayList<>(observations.size());
for (BvhHeuristicTimingSupport.Observation observation : observations) {
records.add(new BvhExplorationCsvWriter.ObservationRecord(
"synthetic",
"calibration",
"synthetic-scenario-catalog",
"controlled-synthetic-scenarios",
BvhExplorationCsvWriter.CANDIDATE_POLICY_VERSION,
observation));
}
return records;
}
private static void addSsiObservations(List<BvhHeuristicTimingSupport.Observation> observations) {
// SSI uses polygons as BVH elements and compares against the brute-force solid check.
for (BvhSyntheticScenarioCatalog.SsiScenario scenario : BvhSyntheticScenarioCatalog.ssiScenarios()) {
List<Polygon> polygons = scenario.geometry.getPolygons();
BvhInputMetricsCollector.Metrics metrics =
......@@ -63,6 +99,7 @@ public class BvhStrategyHeuristicExplorationTest {
}
private static void addNestedObservations(List<BvhHeuristicTimingSupport.Observation> observations) {
// Nested rings use inner rings as searchable elements; the exact containment check stays unchanged.
for (BvhSyntheticScenarioCatalog.NestedScenario scenario : BvhSyntheticScenarioCatalog.nestedScenarios()) {
List<LinearRing> rings = scenario.polygon.getInnerRings();
BvhInputMetricsCollector.Metrics metrics =
......@@ -79,6 +116,7 @@ public class BvhStrategyHeuristicExplorationTest {
}
private static void addRsiObservations(List<BvhHeuristicTimingSupport.Observation> observations) {
// RSI broad-phase behavior is driven by edge boxes rather than whole polygon boxes.
for (BvhSyntheticScenarioCatalog.RsiScenario scenario : BvhSyntheticScenarioCatalog.rsiScenarios()) {
BvhInputMetricsCollector.Metrics metrics =
BvhInputMetricsCollector.collectBoxesCheap(scenario.name, collectEdgeBoxes(scenario.geometry));
......@@ -94,6 +132,7 @@ public class BvhStrategyHeuristicExplorationTest {
}
private static int calculateSsiWithTree(Geometry geometry, SplitStrategy strategy) {
// The tree indexes original polygons; SelfIntersectionUtil still performs the exact intersection checks.
BoundingVolumeHierarchyTree<Polygon> tree =
BoundingVolumeHierarchyTree.newWithStrategy(
geometry.getPolygons(),
......@@ -182,8 +221,8 @@ public class BvhStrategyHeuristicExplorationTest {
metrics.averageRelativeBoxVolume,
metrics.centerSpreadRatio,
metrics.averageAspectRatio,
observation.syntheticPrediction,
observation.candidatePrediction,
observation.currentPolicyPrediction,
observation.candidateRulePrediction,
observation.fastestBvh.variant,
observation.fastestBvh.averageMillis()));
}
......@@ -192,7 +231,9 @@ public class BvhStrategyHeuristicExplorationTest {
System.out.println();
}
private static void printBucketSummary(List<BvhHeuristicTimingSupport.Observation> observations) {
private static List<BvhExplorationCsvWriter.BucketSummaryRecord> printBucketSummary(
List<BvhHeuristicTimingSupport.Observation> observations) {
// Buckets reduce continuous metrics to readable intervals for first-pass rule discovery.
Map<String, BucketStats> buckets = new LinkedHashMap<>();
for (BvhHeuristicTimingSupport.Observation observation : observations) {
addBucket(buckets, observation, "thin", thinBucket(observation.metrics.thinBoxRate));
......@@ -234,6 +275,12 @@ public class BvhStrategyHeuristicExplorationTest {
}
}
System.out.println();
List<BvhExplorationCsvWriter.BucketSummaryRecord> summary = new ArrayList<>(buckets.size());
for (BucketStats bucket : buckets.values()) {
summary.add(bucket.toRecord());
}
return summary;
}
private static void addBucket(
......@@ -312,6 +359,9 @@ public class BvhStrategyHeuristicExplorationTest {
final Map<String, Integer> winners = new LinkedHashMap<>();
int total;
/**
* Counts which split strategy wins inside one metric bucket.
*/
BucketStats(String checkName, String metricName, String bucketName) {
this.checkName = checkName;
this.metricName = metricName;
......@@ -341,6 +391,46 @@ public class BvhStrategyHeuristicExplorationTest {
}
return (double) winners.get(mostCommonWinner()) / total;
}
int winnerCount() {
return winners.getOrDefault(mostCommonWinner(), 0);
}
int secondBestCount() {
int best = 0;
int second = 0;
for (int count : winners.values()) {
if (count > best) {
second = best;
best = count;
} else if (count > second) {
second = count;
}
}
return second;
}
double winnerMargin() {
if (total == 0) {
return 0.0;
}
return (double) (winnerCount() - secondBestCount()) / total;
}
BvhExplorationCsvWriter.BucketSummaryRecord toRecord() {
return new BvhExplorationCsvWriter.BucketSummaryRecord(
checkName,
metricName,
bucketName,
total,
winners,
mostCommonWinner(),
winnerCount(),
mostCommonShare(),
secondBestCount(),
winnerMargin(),
total >= 3 && mostCommonShare() >= 0.65);
}
}
}
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.heuristics;
import de.hft.stuttgart.citydoctor2.checks.aabb.support.BvhExplorationCsvWriter;
import de.hft.stuttgart.citydoctor2.checks.aabb.support.BvhHeuristicTimingSupport;
import de.hft.stuttgart.citydoctor2.checks.aabb.support.BvhInputMetricsCollector;
import static org.junit.Assert.assertFalse;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
......@@ -29,9 +36,9 @@ import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.datastructure.bht.SplitStrategy;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.SplitStrategy;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.visitors.CityObjectCollector;
......@@ -42,6 +49,8 @@ import de.hft.stuttgart.citydoctor2.utils.visitors.CityObjectCollector;
*
* The test measures broad-phase costs and compares candidate strategy rules with
* the fastest measured BVH split strategy.
* Dataset labels come from datasets.csv so calibration and evaluation data can
* be separated without changing the test code.
*
* @author Numanoglu
*/
......@@ -50,14 +59,17 @@ public class RealCityGmlBvhHeuristicExplorationTest {
private static final Path REAL_CITYGML_ROOT =
Path.of("src", "test", "resources", "real-citygml");
private static final Path DATASET_MANIFEST = REAL_CITYGML_ROOT.resolve("datasets.csv");
private static final Path OUTPUT_DIRECTORY = Path.of("target", "bvh-exploration");
private static final double EDGE_BOX_PADDING = 0.001;
private static final int FILE_COLUMN_WIDTH = 28;
private static final String TABLE_FORMAT =
"%-28s | %-6s | %7s | %5s | %7s | %6s | %7s | %23s | %23s | %5s | %9s | %9s | %9s";
"%-28s | %-6s | %7s | %5s | %7s | %6s | %7s | %23s | %23s | %5s | %9s | %9s | %9s | %9s | %9s";
private static final PrintStream SILENT_OUT = new PrintStream(OutputStream.nullOutputStream());
@Test
public void printRealCityGmlMetricsAndBvhTiming() throws Exception {
// Each subdirectory is treated as one labelled dataset group.
assumeTrue(Files.isDirectory(REAL_CITYGML_ROOT),
"Real CityGML test data directory is not available: " + REAL_CITYGML_ROOT);
List<Path> datasetDirectories = listDatasetDirectories(REAL_CITYGML_ROOT);
......@@ -65,15 +77,21 @@ public class RealCityGmlBvhHeuristicExplorationTest {
"Expected real CityGML dataset directories in " + REAL_CITYGML_ROOT);
assertFalse("No real CityGML dataset directories found in " + REAL_CITYGML_ROOT,
datasetDirectories.isEmpty());
Map<String, DatasetMetadata> datasetMetadata = readDatasetManifest(DATASET_MANIFEST);
List<RealObservation> allObservations = new ArrayList<>();
for (Path datasetDirectory : datasetDirectories) {
String datasetName = datasetDirectory.getFileName().toString();
DatasetMetadata metadata = datasetMetadata.get(datasetName);
if (metadata == null) {
throw new IllegalStateException("Dataset is not labelled in " + DATASET_MANIFEST + ": " + datasetName);
}
List<Path> files = listGmlFiles(datasetDirectory);
if (files.isEmpty()) {
continue;
}
printHeader(datasetDirectory.getFileName().toString());
printHeader(datasetName + " / " + metadata.semanticLabel + " / " + metadata.dataSplit);
for (Path file : files) {
ModelElements elements = runQuietly(() -> {
CityDoctorModel model = CityGmlParser.parseCityGmlFile(
......@@ -81,41 +99,87 @@ public class RealCityGmlBvhHeuristicExplorationTest {
new ParserConfiguration(8, false));
return collectElements(model);
});
printAndCollect(allObservations, datasetDirectory, file, "SSI",
printAndCollect(allObservations, metadata, file, "SSI",
measureBroadPhase("SSI", file.getFileName() + ":polygons",
elements.polygonBoxes, QueryMode.INTERSECTING));
printAndCollect(allObservations, datasetDirectory, file, "NESTED",
printAndCollect(allObservations, metadata, file, "NESTED",
measureBroadPhase("NESTED", file.getFileName() + ":innerRings",
elements.innerRingBoxes, QueryMode.CONTAINED));
printAndCollect(allObservations, datasetDirectory, file, "RSI",
printAndCollect(allObservations, metadata, file, "RSI",
measureBroadPhase("RSI", file.getFileName() + ":edges",
elements.edgeBoxes, QueryMode.INTERSECTING));
printSeparator();
}
}
BvhExplorationCsvWriter.writeObservations(
OUTPUT_DIRECTORY.resolve("real_citygml_observations.csv"),
toCsvRecords(allObservations));
printAggregateSummary(allObservations);
printFooter();
}
private static List<BvhExplorationCsvWriter.ObservationRecord> toCsvRecords(
List<RealObservation> observations) {
// Preserve dataset labels in the CSV so pandas can group by split and scenario type later.
List<BvhExplorationCsvWriter.ObservationRecord> records = new ArrayList<>(observations.size());
for (RealObservation observation : observations) {
records.add(new BvhExplorationCsvWriter.ObservationRecord(
"real-citygml",
observation.dataSplit,
observation.datasetName,
observation.semanticLabel,
BvhExplorationCsvWriter.CANDIDATE_POLICY_VERSION,
observation.observation));
}
return records;
}
private static void printAndCollect(
List<RealObservation> allObservations,
Path datasetDirectory,
DatasetMetadata metadata,
Path file,
String checkName,
BvhHeuristicTimingSupport.Observation observation) {
printObservation(file.getFileName().toString(), observation);
allObservations.add(new RealObservation(
datasetDirectory.getFileName().toString(),
metadata.datasetName,
metadata.semanticLabel,
metadata.dataSplit,
file.getFileName().toString(),
checkName,
observation));
}
private static Map<String, DatasetMetadata> readDatasetManifest(Path manifest) throws IOException {
// The manifest makes the real data split explicit instead of relying on directory-name conventions.
if (!Files.isRegularFile(manifest)) {
throw new IllegalStateException("Real CityGML dataset manifest is missing: " + manifest);
}
Map<String, DatasetMetadata> metadata = new LinkedHashMap<>();
List<String> lines = Files.readAllLines(manifest, StandardCharsets.UTF_8);
for (int i = 1; i < lines.size(); i++) {
String line = lines.get(i).trim();
if (line.isEmpty()) {
continue;
}
String[] columns = line.split(",", -1);
if (columns.length != 4) {
throw new IllegalStateException("Invalid dataset manifest row " + (i + 1) + ": " + line);
}
DatasetMetadata row = new DatasetMetadata(columns[0], columns[1], columns[2]);
if (metadata.put(row.datasetName, row) != null) {
throw new IllegalStateException("Duplicate dataset in manifest: " + row.datasetName);
}
}
return metadata;
}
private static BvhHeuristicTimingSupport.Observation measureBroadPhase(
String checkName,
String scenarioName,
List<AABB> boxes,
QueryMode queryMode) {
// Real-data exploration measures candidate search only; exact validation cost is deliberately excluded.
BvhInputMetricsCollector.Metrics metrics =
BvhInputMetricsCollector.collectBoxesCheap(scenarioName, boxes);
return BvhHeuristicTimingSupport.measureBvhStrategies(
......@@ -127,6 +191,7 @@ public class RealCityGmlBvhHeuristicExplorationTest {
}
private static int runBroadPhase(List<AABB> boxes, QueryMode queryMode, SplitStrategy strategy) {
// Index numbers are enough here because the broad phase only needs to count returned candidates.
List<Integer> indices = new ArrayList<>(boxes.size());
for (int i = 0; i < boxes.size(); i++) {
indices.add(i);
......@@ -147,6 +212,7 @@ public class RealCityGmlBvhHeuristicExplorationTest {
}
private static <T> T runQuietly(ThrowingSupplier<T> operation) throws Exception {
// Parser and database startup logs would otherwise drown the timing table.
Level originalRootLevel = LogManager.getRootLogger().getLevel();
PrintStream originalOut = System.out;
PrintStream originalErr = System.err;
......@@ -186,6 +252,7 @@ public class RealCityGmlBvhHeuristicExplorationTest {
}
private static ModelElements collectElements(CityDoctorModel model) {
// Walk through all city objects because real files often contain more than one building-like feature.
ModelElements elements = new ModelElements();
CityObjectCollector collector = new CityObjectCollector();
model.createFeatureStream()
......@@ -199,6 +266,7 @@ public class RealCityGmlBvhHeuristicExplorationTest {
}
private static void collectFromCityObject(CityObject cityObject, ModelElements elements) {
// Identity tracking prevents the same polygon instance from being counted twice.
for (Geometry geometry : cityObject.getGeometries()) {
if (geometry == null) {
continue;
......@@ -240,7 +308,8 @@ public class RealCityGmlBvhHeuristicExplorationTest {
String header = String.format(
TABLE_FORMAT,
"file", "check", "n", "thin", "relVol", "spread", "aspect",
"candidatePred", "winner", "match", "best ms", "pred ms", "loss ms");
"candidatePred", "winner", "match", "best ms", "pred ms", "regret ms",
"regret %", "slowdown");
printSeparator(header.length());
System.out.println(header);
printSeparator(header.length());
......@@ -259,44 +328,60 @@ public class RealCityGmlBvhHeuristicExplorationTest {
String.format("%.5f", metrics.averageRelativeBoxVolume),
String.format("%.3f", metrics.centerSpreadRatio),
String.format("%.1f", metrics.averageAspectRatio),
observation.candidatePrediction,
observation.candidateRulePrediction,
winnerLabel(observation),
observation.candidatePredictionMatchLabel(),
observation.candidateRuleMatchLabel(),
bestMillisLabel(observation),
predictedMillisLabel(observation),
lossMillisLabel(observation)));
regretMillisLabel(observation),
regretRatioLabel(observation),
slowdownLabel(observation)));
}
private static String winnerLabel(BvhHeuristicTimingSupport.Observation observation) {
if ("OLD".equals(observation.candidatePrediction)) {
if ("OLD".equals(observation.candidateRulePrediction)) {
return "OLD";
}
return observation.fastestBvh.variant;
}
private static String bestMillisLabel(BvhHeuristicTimingSupport.Observation observation) {
if ("OLD".equals(observation.candidatePrediction)) {
if ("OLD".equals(observation.candidateRulePrediction)) {
return "-";
}
return String.format("%.3f", observation.fastestBvh.averageMillis());
}
private static String predictedMillisLabel(BvhHeuristicTimingSupport.Observation observation) {
if (observation.predictedBvh == null) {
if (observation.candidateRuleMeasurement == null) {
return "-";
}
return String.format("%.3f", observation.candidateRuleMeasurement.averageMillis());
}
private static String regretMillisLabel(BvhHeuristicTimingSupport.Observation observation) {
if (observation.candidateRuleMeasurement == null) {
return "-";
}
return String.format("%.3f", observation.candidateRegretMillis());
}
private static String regretRatioLabel(BvhHeuristicTimingSupport.Observation observation) {
if (!Double.isFinite(observation.candidateRegretRatio())) {
return "-";
}
return String.format("%.3f", observation.predictedBvh.averageMillis());
return String.format("%.1f%%", observation.candidateRegretRatio() * 100.0);
}
private static String lossMillisLabel(BvhHeuristicTimingSupport.Observation observation) {
if (observation.predictedBvh == null) {
private static String slowdownLabel(BvhHeuristicTimingSupport.Observation observation) {
if (!Double.isFinite(observation.candidateSlowdownFactor())) {
return "-";
}
double lossMillis = observation.predictedBvh.averageMillis() - observation.fastestBvh.averageMillis();
return String.format("%.3f", lossMillis);
return String.format("%.2fx", observation.candidateSlowdownFactor());
}
private static void printAggregateSummary(List<RealObservation> observations) {
// The summary is a quick plausibility check before exporting richer tables with pandas.
System.out.println();
System.out.println("[REAL-CITYGML-BVH-HEURISTIC-SUMMARY]");
System.out.println(String.format(
......@@ -333,10 +418,10 @@ public class RealCityGmlBvhHeuristicExplorationTest {
private static void printCheckSummary(String checkName, List<RealObservation> observations) {
long tested = observations.stream()
.filter(observation -> !"OLD".equals(observation.observation.candidatePrediction))
.filter(observation -> !"OLD".equals(observation.observation.candidateRulePrediction))
.count();
long matches = observations.stream()
.filter(observation -> "yes".equals(observation.observation.candidatePredictionMatchLabel()))
.filter(observation -> "yes".equals(observation.observation.candidateRuleMatchLabel()))
.count();
String accuracy = tested == 0L ? "n/a" : String.format("%.1f%%", 100.0 * matches / tested);
System.out.println(String.format(
......@@ -402,7 +487,7 @@ public class RealCityGmlBvhHeuristicExplorationTest {
private static void printSeparator() {
printSeparator(String.format(
TABLE_FORMAT,
"", "", "", "", "", "", "", "", "", "", "", "", "").length());
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "").length());
}
private static void printSeparator(int width) {
......@@ -432,23 +517,41 @@ public class RealCityGmlBvhHeuristicExplorationTest {
}
private static final class RealObservation {
final String datasetName;
final String datasetName;
final String semanticLabel;
final String dataSplit;
final String fileName;
final String checkName;
final BvhHeuristicTimingSupport.Observation observation;
RealObservation(
String datasetName,
String semanticLabel,
String dataSplit,
String fileName,
String checkName,
BvhHeuristicTimingSupport.Observation observation) {
this.datasetName = datasetName;
this.semanticLabel = semanticLabel;
this.dataSplit = dataSplit;
this.fileName = fileName;
this.checkName = checkName;
this.observation = observation;
}
}
private static final class DatasetMetadata {
final String datasetName;
final String semanticLabel;
final String dataSplit;
DatasetMetadata(String datasetName, String semanticLabel, String dataSplit) {
this.datasetName = datasetName;
this.semanticLabel = semanticLabel;
this.dataSplit = dataSplit;
}
}
private interface MetricValue {
double value(RealObservation observation);
}
......
package de.hft.stuttgart.citydoctor2.checks.bht;
package de.hft.stuttgart.citydoctor2.checks.aabb.performance;
import de.hft.stuttgart.citydoctor2.checks.aabb.fixtures.SyntheticNestedRingGeometryFactory;
import de.hft.stuttgart.citydoctor2.checks.aabb.support.BvhInputMetricsCollector;
import de.hft.stuttgart.citydoctor2.checks.aabb.support.BvhPerformanceTestSupport;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
......@@ -12,8 +16,17 @@ import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.checks.geometry.NestedRingsCheck;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.bht.SplitStrategy;
import de.hft.stuttgart.citydoctor2.datastructure.aabb.SplitStrategy;
/**
* Manual performance probe for nested inner-ring detection.
*
* The old check, the simple AABB filter, and all BVH variants are run on the
* same synthetic polygon so correctness and candidate-search cost can be read
* from one table.
*
* @author Numanoglu
*/
@Tag("performance")
public class NestedRingCheckBvhPerformanceTest {
......@@ -28,6 +41,7 @@ public class NestedRingCheckBvhPerformanceTest {
}
private static void measureScenario(String scenario, ConcretePolygon polygon) {
// Inner rings are the indexed elements; the exact nested-ring decision stays in NestedRingsCheck.
int ringCount = polygon.getInnerRings().size();
BvhInputMetricsCollector.print(BvhInputMetricsCollector.forRings(scenario, polygon.getInnerRings()));
List<BvhPerformanceTestSupport.Measurement> measurements = new ArrayList<>();
......@@ -69,6 +83,7 @@ public class NestedRingCheckBvhPerformanceTest {
}
private static NestedRingsCheck.Variant variantFor(SplitStrategy strategy) {
// The production check exposes concrete BVH strategies through its Variant enum.
switch (strategy) {
case BINARY_OBJECT_MEDIAN:
return NestedRingsCheck.Variant.BVH_BINARY_OBJECT_MEDIAN;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment