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; ...@@ -4,8 +4,8 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB; import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.SplitStrategy; import de.hft.stuttgart.citydoctor2.datastructure.aabb.SplitStrategy;
/** /**
* Central policy for cheap BVH decisions in geometry checks. * Central policy for cheap BVH decisions in geometry checks.
...@@ -250,7 +250,7 @@ public final class BvhUsagePolicy { ...@@ -250,7 +250,7 @@ public final class BvhUsagePolicy {
public static boolean shouldUseTree(BvhCheckType checkType, BvhInputSummary summary) { public static boolean shouldUseTree(BvhCheckType checkType, BvhInputSummary summary) {
int elementCount = summary.getElementCount(); int elementCount = summary.getElementCount();
if (elementCount < 2) { if (elementCount < 20) {
return false; return false;
} }
if (elementCount < thresholdFor(checkType)) { if (elementCount < thresholdFor(checkType)) {
......
...@@ -49,8 +49,8 @@ import de.hft.stuttgart.citydoctor2.datastructure.LinearRing; ...@@ -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.LinearRing.LinearRingType;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex; import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB; import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree; import de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.edge.EdgePolygon; import de.hft.stuttgart.citydoctor2.edge.EdgePolygon;
import de.hft.stuttgart.citydoctor2.edge.IntersectPlanarPolygons; import de.hft.stuttgart.citydoctor2.edge.IntersectPlanarPolygons;
import de.hft.stuttgart.citydoctor2.edge.MeshSurface; 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.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
...@@ -15,9 +19,17 @@ import de.hft.stuttgart.citydoctor2.datastructure.LinearRing; ...@@ -15,9 +19,17 @@ import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Lod; import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex; import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB; import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.SplitStrategy; 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 { public class BvhUsagePolicyTest {
@Test @Test
...@@ -71,10 +83,12 @@ public class BvhUsagePolicyTest { ...@@ -71,10 +83,12 @@ public class BvhUsagePolicyTest {
} }
private static BvhUsagePolicy.BvhInputSummary summaryForPolygons(Geometry geometry) { 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())); return BvhUsagePolicy.BvhInputSummary.ofElements(geometry.getPolygons(), polygon -> AABB.of(polygon.getOriginal()));
} }
private static List<AABB> edgeBoxes(Geometry geometry) { private static List<AABB> edgeBoxes(Geometry geometry) {
// Ring self-intersection uses segment boxes rather than whole polygon boxes.
List<AABB> boxes = new ArrayList<>(); List<AABB> boxes = new ArrayList<>();
for (Polygon polygon : geometry.getPolygons()) { for (Polygon polygon : geometry.getPolygons()) {
LinearRing ring = polygon.getExteriorRing(); 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.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.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex; import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
//import de.hft.stuttgart.citydoctor2.checks.geometry.*; import de.hft.stuttgart.citydoctor2.checks.aabb.NestedRingsCheckAABB;
//import de.hft.stuttgart.citydoctor2.checks.aabb.*; //<-evtl *geomCheks hier verlagern import de.hft.stuttgart.citydoctor2.checks.aabb.RingSelfIntCheckAABB;
import de.hft.stuttgart.citydoctor2.datastructure.*; 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 class GeometryChecksWithAABBTest {
public static void main(String[] args) { 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(); //testTooFewPoints();
testNestedRings(); testNestedRings();
...@@ -95,7 +103,7 @@ public class GeometryChecksWithAABBTest { ...@@ -95,7 +103,7 @@ public class GeometryChecksWithAABBTest {
ConcretePolygon p = new ConcretePolygon(); ConcretePolygon p = new ConcretePolygon();
p.setExteriorRing(ring); 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); Geometry g = new Geometry(GeometryType.SOLID, Lod.LOD2, Geometry.Orientation.OUTWARD);
g.addPolygon(p); // ensures parent links exist 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.assertEquals;
import static org.junit.Assert.assertFalse; 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.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; 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.assertEquals;
import static org.junit.Assert.assertNotNull; 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.assertEquals;
import static org.junit.Assert.assertNotNull; 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.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
...@@ -18,8 +18,8 @@ import de.hft.stuttgart.citydoctor2.datastructure.Geometry; ...@@ -18,8 +18,8 @@ import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.GeometryType; import de.hft.stuttgart.citydoctor2.datastructure.GeometryType;
import de.hft.stuttgart.citydoctor2.datastructure.Lod; import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB; import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree; import de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.exceptions.CityDoctorWriteException; import de.hft.stuttgart.citydoctor2.exceptions.CityDoctorWriteException;
import de.hft.stuttgart.citydoctor2.checks.util.GeometryTestUtils; import de.hft.stuttgart.citydoctor2.checks.util.GeometryTestUtils;
import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil; 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.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
...@@ -19,8 +19,8 @@ import de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType; ...@@ -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.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex; import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB; import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree; import de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil; import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection; 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.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
...@@ -14,9 +16,9 @@ import org.junit.jupiter.api.Test; ...@@ -14,9 +16,9 @@ import org.junit.jupiter.api.Test;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry; import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.Lod; import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB; import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree; import de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.datastructure.bht.SplitStrategy; import de.hft.stuttgart.citydoctor2.datastructure.aabb.SplitStrategy;
import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil; import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection; 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.*; import static org.junit.Assert.*;
...@@ -18,8 +18,8 @@ import de.hft.stuttgart.citydoctor2.parser.CityGmlParser; ...@@ -18,8 +18,8 @@ import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException; import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil; import de.hft.stuttgart.citydoctor2.checks.util.SelfIntersectionUtil;
import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection; import de.hft.stuttgart.citydoctor2.utils.PolygonIntersection;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB; import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree; import de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree;
/* /*
* First comparison SolidSelfIntersection version with Bounding Volume Tree vs old version * 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; import java.util.List;
...@@ -15,14 +15,14 @@ import de.hft.stuttgart.citydoctor2.datastructure.Lod; ...@@ -15,14 +15,14 @@ import de.hft.stuttgart.citydoctor2.datastructure.Lod;
* *
* @author Numanoglu * @author Numanoglu
*/ */
final class BvhSyntheticScenarioCatalog { public final class BvhSyntheticScenarioCatalog {
private static final double EPSILON = 0.001; private static final double EPSILON = 0.001;
private BvhSyntheticScenarioCatalog() { private BvhSyntheticScenarioCatalog() {
} }
static List<SsiScenario> ssiScenarios() { public static List<SsiScenario> ssiScenarios() {
return List.of( return List.of(
new SsiScenario("sep-grid-small", new SsiScenario("sep-grid-small",
SyntheticSolidGeometryFactory.separatedBoxGrid(Lod.LOD2, 6, 6)), SyntheticSolidGeometryFactory.separatedBoxGrid(Lod.LOD2, 6, 6)),
...@@ -74,7 +74,7 @@ final class BvhSyntheticScenarioCatalog { ...@@ -74,7 +74,7 @@ final class BvhSyntheticScenarioCatalog {
SyntheticCityGmlLikeGeometryFactory.variedRoofDistrict(Lod.LOD2, 240))); SyntheticCityGmlLikeGeometryFactory.variedRoofDistrict(Lod.LOD2, 240)));
} }
static List<NestedScenario> nestedScenarios() { public static List<NestedScenario> nestedScenarios() {
return List.of( return List.of(
new NestedScenario("disjoint-small", new NestedScenario("disjoint-small",
SyntheticNestedRingGeometryFactory.manyDisjointInnerRings(120)), SyntheticNestedRingGeometryFactory.manyDisjointInnerRings(120)),
...@@ -108,7 +108,7 @@ final class BvhSyntheticScenarioCatalog { ...@@ -108,7 +108,7 @@ final class BvhSyntheticScenarioCatalog {
SyntheticNestedRingGeometryFactory.spreadSweepInnerRings(240, 20.0))); SyntheticNestedRingGeometryFactory.spreadSweepInnerRings(240, 20.0)));
} }
static List<RsiScenario> rsiScenarios() { public static List<RsiScenario> rsiScenarios() {
return List.of( return List.of(
new RsiScenario("rsi-small", new RsiScenario("rsi-small",
SyntheticRingGeometryFactory.performanceRingGeometry(200, 100, EPSILON)), SyntheticRingGeometryFactory.performanceRingGeometry(200, 100, EPSILON)),
...@@ -132,9 +132,9 @@ final class BvhSyntheticScenarioCatalog { ...@@ -132,9 +132,9 @@ final class BvhSyntheticScenarioCatalog {
SyntheticRingGeometryFactory.spreadSweepGeometry(240, 30.0))); SyntheticRingGeometryFactory.spreadSweepGeometry(240, 30.0)));
} }
static final class SsiScenario { public static final class SsiScenario {
final String name; public final String name;
final Geometry geometry; public final Geometry geometry;
SsiScenario(String name, Geometry geometry) { SsiScenario(String name, Geometry geometry) {
this.name = name; this.name = name;
...@@ -142,9 +142,9 @@ final class BvhSyntheticScenarioCatalog { ...@@ -142,9 +142,9 @@ final class BvhSyntheticScenarioCatalog {
} }
} }
static final class NestedScenario { public static final class NestedScenario {
final String name; public final String name;
final ConcretePolygon polygon; public final ConcretePolygon polygon;
NestedScenario(String name, ConcretePolygon polygon) { NestedScenario(String name, ConcretePolygon polygon) {
this.name = name; this.name = name;
...@@ -152,9 +152,9 @@ final class BvhSyntheticScenarioCatalog { ...@@ -152,9 +152,9 @@ final class BvhSyntheticScenarioCatalog {
} }
} }
static final class RsiScenario { public static final class RsiScenario {
final String name; public final String name;
final Geometry geometry; public final Geometry geometry;
RsiScenario(String name, Geometry geometry) { RsiScenario(String name, Geometry geometry) {
this.name = name; 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.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry; import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
...@@ -19,7 +19,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.Vertex; ...@@ -19,7 +19,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
* *
* @author Numanoglu * @author Numanoglu
*/ */
final class SyntheticCityGmlLikeGeometryFactory { public final class SyntheticCityGmlLikeGeometryFactory {
private SyntheticCityGmlLikeGeometryFactory() { private SyntheticCityGmlLikeGeometryFactory() {
} }
...@@ -28,7 +28,7 @@ final class SyntheticCityGmlLikeGeometryFactory { ...@@ -28,7 +28,7 @@ final class SyntheticCityGmlLikeGeometryFactory {
* Creates several blocks with mixed compact, elongated, and roof-like buildings. * Creates several blocks with mixed compact, elongated, and roof-like buildings.
* Useful as a balanced city-district fixture with moderate spatial separation. * 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); Geometry geometry = newSolid(lod);
for (int blockX = 0; blockX < blockColumns; blockX++) { for (int blockX = 0; blockX < blockColumns; blockX++) {
for (int blockY = 0; blockY < blockRows; blockY++) { for (int blockY = 0; blockY < blockRows; blockY++) {
...@@ -45,7 +45,7 @@ final class SyntheticCityGmlLikeGeometryFactory { ...@@ -45,7 +45,7 @@ final class SyntheticCityGmlLikeGeometryFactory {
* Creates block-perimeter buildings around open courtyards. This produces many * Creates block-perimeter buildings around open courtyards. This produces many
* nearby but not necessarily intersecting AABBs, closer to dense urban blocks. * 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); Geometry geometry = newSolid(lod);
for (int block = 0; block < blockCount; block++) { for (int block = 0; block < blockCount; block++) {
double originX = (block % 4) * 62.0; double originX = (block % 4) * 62.0;
...@@ -64,7 +64,7 @@ final class SyntheticCityGmlLikeGeometryFactory { ...@@ -64,7 +64,7 @@ final class SyntheticCityGmlLikeGeometryFactory {
* Creates many small buildings with alternating flat and gabled roof shapes. * Creates many small buildings with alternating flat and gabled roof shapes.
* Useful for a larger polygon count with city-like height and footprint jitter. * 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); Geometry geometry = newSolid(lod);
for (int i = 0; i < buildingCount; i++) { for (int i = 0; i < buildingCount; i++) {
double x = (i % 12) * 13.0 + ((i / 12) % 2) * 4.0; 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.io.File;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -56,12 +56,12 @@ import de.hft.stuttgart.citydoctor2.datastructure.Vertex; ...@@ -56,12 +56,12 @@ import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
* @author Numanoglu * @author Numanoglu
*/ */
final class SyntheticNestedRingGeometryFactory { public final class SyntheticNestedRingGeometryFactory {
private SyntheticNestedRingGeometryFactory() { private SyntheticNestedRingGeometryFactory() {
} }
static Geometry complexNestedRingGeometry() { public static Geometry complexNestedRingGeometry() {
Geometry geometry = new Geometry(GeometryType.SOLID, Lod.LOD2, Orientation.OUTWARD); Geometry geometry = new Geometry(GeometryType.SOLID, Lod.LOD2, Orientation.OUTWARD);
for (ConcretePolygon polygon : complexNestedRingPolygons()) { for (ConcretePolygon polygon : complexNestedRingPolygons()) {
geometry.addPolygon(polygon); geometry.addPolygon(polygon);
...@@ -70,7 +70,7 @@ final class SyntheticNestedRingGeometryFactory { ...@@ -70,7 +70,7 @@ final class SyntheticNestedRingGeometryFactory {
return geometry; return geometry;
} }
static List<ConcretePolygon> complexNestedRingPolygons() { public static List<ConcretePolygon> complexNestedRingPolygons() {
List<ConcretePolygon> polygons = new ArrayList<>(); List<ConcretePolygon> polygons = new ArrayList<>();
polygons.add(manyDisjointInnerRings(100)); polygons.add(manyDisjointInnerRings(100));
polygons.add(oneNestedPairAmongMany(96)); polygons.add(oneNestedPairAmongMany(96));
...@@ -81,7 +81,7 @@ final class SyntheticNestedRingGeometryFactory { ...@@ -81,7 +81,7 @@ final class SyntheticNestedRingGeometryFactory {
return polygons; return polygons;
} }
static ConcretePolygon manyDisjointInnerRings(int count) { public static ConcretePolygon manyDisjointInnerRings(int count) {
ConcretePolygon polygon = basePolygon(count); ConcretePolygon polygon = basePolygon(count);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
double x = gridX(i, 10) * 8.0 + 4.0; double x = gridX(i, 10) * 8.0 + 4.0;
...@@ -91,14 +91,14 @@ final class SyntheticNestedRingGeometryFactory { ...@@ -91,14 +91,14 @@ final class SyntheticNestedRingGeometryFactory {
return polygon; return polygon;
} }
static ConcretePolygon oneNestedPairAmongMany(int count) { public static ConcretePolygon oneNestedPairAmongMany(int count) {
ConcretePolygon polygon = manyDisjointInnerRings(Math.max(0, count - 2)); ConcretePolygon polygon = manyDisjointInnerRings(Math.max(0, count - 2));
polygon.addInteriorRing(squareRing(5.0, 85.0, 5.0)); polygon.addInteriorRing(squareRing(5.0, 85.0, 5.0));
polygon.addInteriorRing(squareRing(5.0, 85.0, 1.0)); polygon.addInteriorRing(squareRing(5.0, 85.0, 1.0));
return polygon; return polygon;
} }
static ConcretePolygon concentricNestedRings(int count) { public static ConcretePolygon concentricNestedRings(int count) {
ConcretePolygon polygon = basePolygon(count); ConcretePolygon polygon = basePolygon(count);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
double halfSize = Math.max(1.0, count - i); double halfSize = Math.max(1.0, count - i);
...@@ -107,7 +107,7 @@ final class SyntheticNestedRingGeometryFactory { ...@@ -107,7 +107,7 @@ final class SyntheticNestedRingGeometryFactory {
return polygon; return polygon;
} }
static ConcretePolygon overlappingAabbsButNotNested(int count) { public static ConcretePolygon overlappingAabbsButNotNested(int count) {
ConcretePolygon polygon = basePolygon(count); ConcretePolygon polygon = basePolygon(count);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
double x = 6.0 + (i % 12) * 6.0; double x = 6.0 + (i % 12) * 6.0;
...@@ -118,7 +118,7 @@ final class SyntheticNestedRingGeometryFactory { ...@@ -118,7 +118,7 @@ final class SyntheticNestedRingGeometryFactory {
return polygon; return polygon;
} }
static ConcretePolygon clusteredInnerRings(int clusterCount, int ringsPerCluster) { public static ConcretePolygon clusteredInnerRings(int clusterCount, int ringsPerCluster) {
ConcretePolygon polygon = basePolygon(clusterCount * ringsPerCluster); ConcretePolygon polygon = basePolygon(clusterCount * ringsPerCluster);
for (int cluster = 0; cluster < clusterCount; cluster++) { for (int cluster = 0; cluster < clusterCount; cluster++) {
double clusterX = 8.0 + (cluster % 4) * 22.0; double clusterX = 8.0 + (cluster % 4) * 22.0;
...@@ -132,14 +132,14 @@ final class SyntheticNestedRingGeometryFactory { ...@@ -132,14 +132,14 @@ final class SyntheticNestedRingGeometryFactory {
return polygon; return polygon;
} }
static ConcretePolygon clusteredInnerRingsWithNestedPair(int clusterCount, int ringsPerCluster) { public static ConcretePolygon clusteredInnerRingsWithNestedPair(int clusterCount, int ringsPerCluster) {
ConcretePolygon polygon = clusteredInnerRings(clusterCount, ringsPerCluster); ConcretePolygon polygon = clusteredInnerRings(clusterCount, ringsPerCluster);
polygon.addInteriorRing(squareRing(88.0, 88.0, 5.0)); polygon.addInteriorRing(squareRing(88.0, 88.0, 5.0));
polygon.addInteriorRing(squareRing(88.0, 88.0, 1.0)); polygon.addInteriorRing(squareRing(88.0, 88.0, 1.0));
return polygon; 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)); int columns = (int) Math.ceil(Math.sqrt(count));
double width = Math.max(1.0, aspectRatio); double width = Math.max(1.0, aspectRatio);
double extent = columns * (width + 4.0) + 20.0; double extent = columns * (width + 4.0) + 20.0;
...@@ -152,7 +152,7 @@ final class SyntheticNestedRingGeometryFactory { ...@@ -152,7 +152,7 @@ final class SyntheticNestedRingGeometryFactory {
return polygon; 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)); int columns = (int) Math.ceil(Math.sqrt(count));
double extent = columns * spacing + 20.0; double extent = columns * spacing + 20.0;
ConcretePolygon polygon = basePolygonWithExtent(extent); ConcretePolygon polygon = basePolygonWithExtent(extent);
......
package de.hft.stuttgart.citydoctor2.checks.bht; package de.hft.stuttgart.citydoctor2.checks.aabb.fixtures;
import java.io.File; import java.io.File;
...@@ -42,12 +42,12 @@ import de.hft.stuttgart.citydoctor2.datastructure.Vertex; ...@@ -42,12 +42,12 @@ import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
* *
* @author Numanoglu * @author Numanoglu
*/ */
final class SyntheticRingGeometryFactory { public final class SyntheticRingGeometryFactory {
private SyntheticRingGeometryFactory() { private SyntheticRingGeometryFactory() {
} }
static Geometry correctnessRingGeometry(double epsilon) { public static Geometry correctnessRingGeometry(double epsilon) {
Geometry geometry = new Geometry(GeometryType.SOLID, Lod.LOD2, Orientation.OUTWARD); Geometry geometry = new Geometry(GeometryType.SOLID, Lod.LOD2, Orientation.OUTWARD);
addRingPolygon(geometry, rectangle()); addRingPolygon(geometry, rectangle());
addRingPolygon(geometry, bowTie()); addRingPolygon(geometry, bowTie());
...@@ -58,7 +58,7 @@ final class SyntheticRingGeometryFactory { ...@@ -58,7 +58,7 @@ final class SyntheticRingGeometryFactory {
return geometry; 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); Geometry geometry = new Geometry(GeometryType.SOLID, Lod.LOD2, Orientation.OUTWARD);
addRingPolygon(geometry, largeConvexRing(convexVertexCount, 0.0, 0.0, 0.0)); addRingPolygon(geometry, largeConvexRing(convexVertexCount, 0.0, 0.0, 0.0));
addRingPolygon(geometry, zigZagCorridor(zigZagSegments)); addRingPolygon(geometry, zigZagCorridor(zigZagSegments));
...@@ -68,7 +68,7 @@ final class SyntheticRingGeometryFactory { ...@@ -68,7 +68,7 @@ final class SyntheticRingGeometryFactory {
return geometry; 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); Geometry geometry = new Geometry(GeometryType.SOLID, Lod.LOD2, Orientation.OUTWARD);
double width = Math.max(1.0, aspectRatio); double width = Math.max(1.0, aspectRatio);
for (int i = 0; i < ringCount; i++) { for (int i = 0; i < ringCount; i++) {
...@@ -80,7 +80,7 @@ final class SyntheticRingGeometryFactory { ...@@ -80,7 +80,7 @@ final class SyntheticRingGeometryFactory {
return geometry; 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); Geometry geometry = new Geometry(GeometryType.SOLID, Lod.LOD2, Orientation.OUTWARD);
for (int i = 0; i < ringCount; i++) { for (int i = 0; i < ringCount; i++) {
double x = (i % 20) * spacing; double x = (i % 20) * spacing;
......
package de.hft.stuttgart.citydoctor2.checks.bht; package de.hft.stuttgart.citydoctor2.checks.aabb.fixtures;
import java.io.File; import java.io.File;
...@@ -30,12 +30,12 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration; ...@@ -30,12 +30,12 @@ import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
* *
* @author Numanoglu * @author Numanoglu
*/ */
final class SyntheticSolidGeometryFactory { public final class SyntheticSolidGeometryFactory {
private 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); Geometry geometry = newSolid(lod);
double spacing = 6.0; double spacing = 6.0;
for (int ix = 0; ix < xCount; ix++) { for (int ix = 0; ix < xCount; ix++) {
...@@ -47,7 +47,7 @@ final class SyntheticSolidGeometryFactory { ...@@ -47,7 +47,7 @@ final class SyntheticSolidGeometryFactory {
return geometry; 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); Geometry geometry = separatedBoxGrid(lod, xCount, yCount);
addBox(geometry, 6.0, 6.0, 0.5, 6.0, 2.8, 3.5); 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); addBox(geometry, 8.0, 5.5, 0.0, 2.8, 6.0, 4.2);
...@@ -55,7 +55,7 @@ final class SyntheticSolidGeometryFactory { ...@@ -55,7 +55,7 @@ final class SyntheticSolidGeometryFactory {
return geometry; 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); Geometry geometry = newSolid(lod);
double clusterSpacing = 30.0; double clusterSpacing = 30.0;
for (int cluster = 0; cluster < clusterCount; cluster++) { for (int cluster = 0; cluster < clusterCount; cluster++) {
...@@ -70,7 +70,7 @@ final class SyntheticSolidGeometryFactory { ...@@ -70,7 +70,7 @@ final class SyntheticSolidGeometryFactory {
return geometry; return geometry;
} }
static Geometry longThinSlabs(Lod lod, int count) { public static Geometry longThinSlabs(Lod lod, int count) {
Geometry geometry = newSolid(lod); Geometry geometry = newSolid(lod);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
double y = i * 0.75; double y = i * 0.75;
...@@ -80,7 +80,7 @@ final class SyntheticSolidGeometryFactory { ...@@ -80,7 +80,7 @@ final class SyntheticSolidGeometryFactory {
return geometry; return geometry;
} }
static Geometry flatBoxGrid(Lod lod, int count) { public static Geometry flatBoxGrid(Lod lod, int count) {
Geometry geometry = newSolid(lod); Geometry geometry = newSolid(lod);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
double x = (i % 10) * 5.0; double x = (i % 10) * 5.0;
...@@ -91,7 +91,7 @@ final class SyntheticSolidGeometryFactory { ...@@ -91,7 +91,7 @@ final class SyntheticSolidGeometryFactory {
return geometry; 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); Geometry geometry = newSolid(lod);
int columns = (int) Math.ceil(Math.sqrt(count)); int columns = (int) Math.ceil(Math.sqrt(count));
int thinCount = (int) Math.round(count * thinRatio); int thinCount = (int) Math.round(count * thinRatio);
...@@ -108,7 +108,7 @@ final class SyntheticSolidGeometryFactory { ...@@ -108,7 +108,7 @@ final class SyntheticSolidGeometryFactory {
return geometry; 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); Geometry geometry = newSolid(lod);
int columns = (int) Math.ceil(Math.sqrt(count)); int columns = (int) Math.ceil(Math.sqrt(count));
double width = Math.max(1.0, aspectRatio); double width = Math.max(1.0, aspectRatio);
...@@ -122,7 +122,7 @@ final class SyntheticSolidGeometryFactory { ...@@ -122,7 +122,7 @@ final class SyntheticSolidGeometryFactory {
return geometry; 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); Geometry geometry = newSolid(lod);
int columns = (int) Math.ceil(Math.sqrt(count)); int columns = (int) Math.ceil(Math.sqrt(count));
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
...@@ -134,7 +134,7 @@ final class SyntheticSolidGeometryFactory { ...@@ -134,7 +134,7 @@ final class SyntheticSolidGeometryFactory {
return geometry; 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); Geometry geometry = newSolid(lod);
int columns = (int) Math.ceil(Math.sqrt(count)); int columns = (int) Math.ceil(Math.sqrt(count));
double spacing = boxSize * spacingFactor; 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.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
...@@ -19,14 +26,18 @@ import de.hft.stuttgart.citydoctor2.datastructure.Geometry; ...@@ -19,14 +26,18 @@ import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing; import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex; import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB; import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree; import de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.datastructure.bht.SplitStrategy; import de.hft.stuttgart.citydoctor2.datastructure.aabb.SplitStrategy;
/** /**
* Collects compact timing observations and broad-phase metrics for BVH strategy * Collects compact timing observations and broad-phase metrics for BVH strategy
* selection from synthetic fixtures. * 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 * @author Numanoglu
*/ */
@Tag("performance") @Tag("performance")
...@@ -34,19 +45,44 @@ public class BvhStrategyHeuristicExplorationTest { ...@@ -34,19 +45,44 @@ public class BvhStrategyHeuristicExplorationTest {
private static final double DELTA = 0.001; private static final double DELTA = 0.001;
private static final double EPSILON = 0.001; private static final double EPSILON = 0.001;
private static final Path OUTPUT_DIRECTORY = Path.of("target", "bvh-exploration");
@Test @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<>(); List<BvhHeuristicTimingSupport.Observation> observations = new ArrayList<>();
addSsiObservations(observations); addSsiObservations(observations);
addNestedObservations(observations); addNestedObservations(observations);
addRsiObservations(observations); addRsiObservations(observations);
printObservationSummary(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) { 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()) { for (BvhSyntheticScenarioCatalog.SsiScenario scenario : BvhSyntheticScenarioCatalog.ssiScenarios()) {
List<Polygon> polygons = scenario.geometry.getPolygons(); List<Polygon> polygons = scenario.geometry.getPolygons();
BvhInputMetricsCollector.Metrics metrics = BvhInputMetricsCollector.Metrics metrics =
...@@ -63,6 +99,7 @@ public class BvhStrategyHeuristicExplorationTest { ...@@ -63,6 +99,7 @@ public class BvhStrategyHeuristicExplorationTest {
} }
private static void addNestedObservations(List<BvhHeuristicTimingSupport.Observation> observations) { 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()) { for (BvhSyntheticScenarioCatalog.NestedScenario scenario : BvhSyntheticScenarioCatalog.nestedScenarios()) {
List<LinearRing> rings = scenario.polygon.getInnerRings(); List<LinearRing> rings = scenario.polygon.getInnerRings();
BvhInputMetricsCollector.Metrics metrics = BvhInputMetricsCollector.Metrics metrics =
...@@ -79,6 +116,7 @@ public class BvhStrategyHeuristicExplorationTest { ...@@ -79,6 +116,7 @@ public class BvhStrategyHeuristicExplorationTest {
} }
private static void addRsiObservations(List<BvhHeuristicTimingSupport.Observation> observations) { 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()) { for (BvhSyntheticScenarioCatalog.RsiScenario scenario : BvhSyntheticScenarioCatalog.rsiScenarios()) {
BvhInputMetricsCollector.Metrics metrics = BvhInputMetricsCollector.Metrics metrics =
BvhInputMetricsCollector.collectBoxesCheap(scenario.name, collectEdgeBoxes(scenario.geometry)); BvhInputMetricsCollector.collectBoxesCheap(scenario.name, collectEdgeBoxes(scenario.geometry));
...@@ -94,6 +132,7 @@ public class BvhStrategyHeuristicExplorationTest { ...@@ -94,6 +132,7 @@ public class BvhStrategyHeuristicExplorationTest {
} }
private static int calculateSsiWithTree(Geometry geometry, SplitStrategy strategy) { private static int calculateSsiWithTree(Geometry geometry, SplitStrategy strategy) {
// The tree indexes original polygons; SelfIntersectionUtil still performs the exact intersection checks.
BoundingVolumeHierarchyTree<Polygon> tree = BoundingVolumeHierarchyTree<Polygon> tree =
BoundingVolumeHierarchyTree.newWithStrategy( BoundingVolumeHierarchyTree.newWithStrategy(
geometry.getPolygons(), geometry.getPolygons(),
...@@ -182,8 +221,8 @@ public class BvhStrategyHeuristicExplorationTest { ...@@ -182,8 +221,8 @@ public class BvhStrategyHeuristicExplorationTest {
metrics.averageRelativeBoxVolume, metrics.averageRelativeBoxVolume,
metrics.centerSpreadRatio, metrics.centerSpreadRatio,
metrics.averageAspectRatio, metrics.averageAspectRatio,
observation.syntheticPrediction, observation.currentPolicyPrediction,
observation.candidatePrediction, observation.candidateRulePrediction,
observation.fastestBvh.variant, observation.fastestBvh.variant,
observation.fastestBvh.averageMillis())); observation.fastestBvh.averageMillis()));
} }
...@@ -192,7 +231,9 @@ public class BvhStrategyHeuristicExplorationTest { ...@@ -192,7 +231,9 @@ public class BvhStrategyHeuristicExplorationTest {
System.out.println(); 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<>(); Map<String, BucketStats> buckets = new LinkedHashMap<>();
for (BvhHeuristicTimingSupport.Observation observation : observations) { for (BvhHeuristicTimingSupport.Observation observation : observations) {
addBucket(buckets, observation, "thin", thinBucket(observation.metrics.thinBoxRate)); addBucket(buckets, observation, "thin", thinBucket(observation.metrics.thinBoxRate));
...@@ -234,6 +275,12 @@ public class BvhStrategyHeuristicExplorationTest { ...@@ -234,6 +275,12 @@ public class BvhStrategyHeuristicExplorationTest {
} }
} }
System.out.println(); 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( private static void addBucket(
...@@ -312,6 +359,9 @@ public class BvhStrategyHeuristicExplorationTest { ...@@ -312,6 +359,9 @@ public class BvhStrategyHeuristicExplorationTest {
final Map<String, Integer> winners = new LinkedHashMap<>(); final Map<String, Integer> winners = new LinkedHashMap<>();
int total; int total;
/**
* Counts which split strategy wins inside one metric bucket.
*/
BucketStats(String checkName, String metricName, String bucketName) { BucketStats(String checkName, String metricName, String bucketName) {
this.checkName = checkName; this.checkName = checkName;
this.metricName = metricName; this.metricName = metricName;
...@@ -341,6 +391,46 @@ public class BvhStrategyHeuristicExplorationTest { ...@@ -341,6 +391,46 @@ public class BvhStrategyHeuristicExplorationTest {
} }
return (double) winners.get(mostCommonWinner()) / total; 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.Assert.assertFalse;
import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
...@@ -29,9 +36,9 @@ import de.hft.stuttgart.citydoctor2.datastructure.Geometry; ...@@ -29,9 +36,9 @@ import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing; import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex; import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.bht.AABB; import de.hft.stuttgart.citydoctor2.datastructure.aabb.AABB;
import de.hft.stuttgart.citydoctor2.datastructure.bht.BoundingVolumeHierarchyTree; import de.hft.stuttgart.citydoctor2.datastructure.aabb.BoundingVolumeHierarchyTree;
import de.hft.stuttgart.citydoctor2.datastructure.bht.SplitStrategy; import de.hft.stuttgart.citydoctor2.datastructure.aabb.SplitStrategy;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParser; import de.hft.stuttgart.citydoctor2.parser.CityGmlParser;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration; import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.utils.visitors.CityObjectCollector; import de.hft.stuttgart.citydoctor2.utils.visitors.CityObjectCollector;
...@@ -42,6 +49,8 @@ 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 test measures broad-phase costs and compares candidate strategy rules with
* the fastest measured BVH split strategy. * 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 * @author Numanoglu
*/ */
...@@ -50,14 +59,17 @@ public class RealCityGmlBvhHeuristicExplorationTest { ...@@ -50,14 +59,17 @@ public class RealCityGmlBvhHeuristicExplorationTest {
private static final Path REAL_CITYGML_ROOT = private static final Path REAL_CITYGML_ROOT =
Path.of("src", "test", "resources", "real-citygml"); 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 double EDGE_BOX_PADDING = 0.001;
private static final int FILE_COLUMN_WIDTH = 28; private static final int FILE_COLUMN_WIDTH = 28;
private static final String TABLE_FORMAT = 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()); private static final PrintStream SILENT_OUT = new PrintStream(OutputStream.nullOutputStream());
@Test @Test
public void printRealCityGmlMetricsAndBvhTiming() throws Exception { public void printRealCityGmlMetricsAndBvhTiming() throws Exception {
// Each subdirectory is treated as one labelled dataset group.
assumeTrue(Files.isDirectory(REAL_CITYGML_ROOT), assumeTrue(Files.isDirectory(REAL_CITYGML_ROOT),
"Real CityGML test data directory is not available: " + REAL_CITYGML_ROOT); "Real CityGML test data directory is not available: " + REAL_CITYGML_ROOT);
List<Path> datasetDirectories = listDatasetDirectories(REAL_CITYGML_ROOT); List<Path> datasetDirectories = listDatasetDirectories(REAL_CITYGML_ROOT);
...@@ -65,15 +77,21 @@ public class RealCityGmlBvhHeuristicExplorationTest { ...@@ -65,15 +77,21 @@ public class RealCityGmlBvhHeuristicExplorationTest {
"Expected real CityGML dataset directories in " + REAL_CITYGML_ROOT); "Expected real CityGML dataset directories in " + REAL_CITYGML_ROOT);
assertFalse("No real CityGML dataset directories found in " + REAL_CITYGML_ROOT, assertFalse("No real CityGML dataset directories found in " + REAL_CITYGML_ROOT,
datasetDirectories.isEmpty()); datasetDirectories.isEmpty());
Map<String, DatasetMetadata> datasetMetadata = readDatasetManifest(DATASET_MANIFEST);
List<RealObservation> allObservations = new ArrayList<>(); List<RealObservation> allObservations = new ArrayList<>();
for (Path datasetDirectory : datasetDirectories) { 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); List<Path> files = listGmlFiles(datasetDirectory);
if (files.isEmpty()) { if (files.isEmpty()) {
continue; continue;
} }
printHeader(datasetDirectory.getFileName().toString()); printHeader(datasetName + " / " + metadata.semanticLabel + " / " + metadata.dataSplit);
for (Path file : files) { for (Path file : files) {
ModelElements elements = runQuietly(() -> { ModelElements elements = runQuietly(() -> {
CityDoctorModel model = CityGmlParser.parseCityGmlFile( CityDoctorModel model = CityGmlParser.parseCityGmlFile(
...@@ -81,41 +99,87 @@ public class RealCityGmlBvhHeuristicExplorationTest { ...@@ -81,41 +99,87 @@ public class RealCityGmlBvhHeuristicExplorationTest {
new ParserConfiguration(8, false)); new ParserConfiguration(8, false));
return collectElements(model); return collectElements(model);
}); });
printAndCollect(allObservations, datasetDirectory, file, "SSI", printAndCollect(allObservations, metadata, file, "SSI",
measureBroadPhase("SSI", file.getFileName() + ":polygons", measureBroadPhase("SSI", file.getFileName() + ":polygons",
elements.polygonBoxes, QueryMode.INTERSECTING)); elements.polygonBoxes, QueryMode.INTERSECTING));
printAndCollect(allObservations, datasetDirectory, file, "NESTED", printAndCollect(allObservations, metadata, file, "NESTED",
measureBroadPhase("NESTED", file.getFileName() + ":innerRings", measureBroadPhase("NESTED", file.getFileName() + ":innerRings",
elements.innerRingBoxes, QueryMode.CONTAINED)); elements.innerRingBoxes, QueryMode.CONTAINED));
printAndCollect(allObservations, datasetDirectory, file, "RSI", printAndCollect(allObservations, metadata, file, "RSI",
measureBroadPhase("RSI", file.getFileName() + ":edges", measureBroadPhase("RSI", file.getFileName() + ":edges",
elements.edgeBoxes, QueryMode.INTERSECTING)); elements.edgeBoxes, QueryMode.INTERSECTING));
printSeparator(); printSeparator();
} }
} }
BvhExplorationCsvWriter.writeObservations(
OUTPUT_DIRECTORY.resolve("real_citygml_observations.csv"),
toCsvRecords(allObservations));
printAggregateSummary(allObservations); printAggregateSummary(allObservations);
printFooter(); 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( private static void printAndCollect(
List<RealObservation> allObservations, List<RealObservation> allObservations,
Path datasetDirectory, DatasetMetadata metadata,
Path file, Path file,
String checkName, String checkName,
BvhHeuristicTimingSupport.Observation observation) { BvhHeuristicTimingSupport.Observation observation) {
printObservation(file.getFileName().toString(), observation); printObservation(file.getFileName().toString(), observation);
allObservations.add(new RealObservation( allObservations.add(new RealObservation(
datasetDirectory.getFileName().toString(), metadata.datasetName,
metadata.semanticLabel,
metadata.dataSplit,
file.getFileName().toString(), file.getFileName().toString(),
checkName, checkName,
observation)); 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( private static BvhHeuristicTimingSupport.Observation measureBroadPhase(
String checkName, String checkName,
String scenarioName, String scenarioName,
List<AABB> boxes, List<AABB> boxes,
QueryMode queryMode) { QueryMode queryMode) {
// Real-data exploration measures candidate search only; exact validation cost is deliberately excluded.
BvhInputMetricsCollector.Metrics metrics = BvhInputMetricsCollector.Metrics metrics =
BvhInputMetricsCollector.collectBoxesCheap(scenarioName, boxes); BvhInputMetricsCollector.collectBoxesCheap(scenarioName, boxes);
return BvhHeuristicTimingSupport.measureBvhStrategies( return BvhHeuristicTimingSupport.measureBvhStrategies(
...@@ -127,6 +191,7 @@ public class RealCityGmlBvhHeuristicExplorationTest { ...@@ -127,6 +191,7 @@ public class RealCityGmlBvhHeuristicExplorationTest {
} }
private static int runBroadPhase(List<AABB> boxes, QueryMode queryMode, SplitStrategy strategy) { 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()); List<Integer> indices = new ArrayList<>(boxes.size());
for (int i = 0; i < boxes.size(); i++) { for (int i = 0; i < boxes.size(); i++) {
indices.add(i); indices.add(i);
...@@ -147,6 +212,7 @@ public class RealCityGmlBvhHeuristicExplorationTest { ...@@ -147,6 +212,7 @@ public class RealCityGmlBvhHeuristicExplorationTest {
} }
private static <T> T runQuietly(ThrowingSupplier<T> operation) throws Exception { 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(); Level originalRootLevel = LogManager.getRootLogger().getLevel();
PrintStream originalOut = System.out; PrintStream originalOut = System.out;
PrintStream originalErr = System.err; PrintStream originalErr = System.err;
...@@ -186,6 +252,7 @@ public class RealCityGmlBvhHeuristicExplorationTest { ...@@ -186,6 +252,7 @@ public class RealCityGmlBvhHeuristicExplorationTest {
} }
private static ModelElements collectElements(CityDoctorModel model) { 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(); ModelElements elements = new ModelElements();
CityObjectCollector collector = new CityObjectCollector(); CityObjectCollector collector = new CityObjectCollector();
model.createFeatureStream() model.createFeatureStream()
...@@ -199,6 +266,7 @@ public class RealCityGmlBvhHeuristicExplorationTest { ...@@ -199,6 +266,7 @@ public class RealCityGmlBvhHeuristicExplorationTest {
} }
private static void collectFromCityObject(CityObject cityObject, ModelElements elements) { private static void collectFromCityObject(CityObject cityObject, ModelElements elements) {
// Identity tracking prevents the same polygon instance from being counted twice.
for (Geometry geometry : cityObject.getGeometries()) { for (Geometry geometry : cityObject.getGeometries()) {
if (geometry == null) { if (geometry == null) {
continue; continue;
...@@ -240,7 +308,8 @@ public class RealCityGmlBvhHeuristicExplorationTest { ...@@ -240,7 +308,8 @@ public class RealCityGmlBvhHeuristicExplorationTest {
String header = String.format( String header = String.format(
TABLE_FORMAT, TABLE_FORMAT,
"file", "check", "n", "thin", "relVol", "spread", "aspect", "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()); printSeparator(header.length());
System.out.println(header); System.out.println(header);
printSeparator(header.length()); printSeparator(header.length());
...@@ -259,44 +328,60 @@ public class RealCityGmlBvhHeuristicExplorationTest { ...@@ -259,44 +328,60 @@ public class RealCityGmlBvhHeuristicExplorationTest {
String.format("%.5f", metrics.averageRelativeBoxVolume), String.format("%.5f", metrics.averageRelativeBoxVolume),
String.format("%.3f", metrics.centerSpreadRatio), String.format("%.3f", metrics.centerSpreadRatio),
String.format("%.1f", metrics.averageAspectRatio), String.format("%.1f", metrics.averageAspectRatio),
observation.candidatePrediction, observation.candidateRulePrediction,
winnerLabel(observation), winnerLabel(observation),
observation.candidatePredictionMatchLabel(), observation.candidateRuleMatchLabel(),
bestMillisLabel(observation), bestMillisLabel(observation),
predictedMillisLabel(observation), predictedMillisLabel(observation),
lossMillisLabel(observation))); regretMillisLabel(observation),
regretRatioLabel(observation),
slowdownLabel(observation)));
} }
private static String winnerLabel(BvhHeuristicTimingSupport.Observation observation) { private static String winnerLabel(BvhHeuristicTimingSupport.Observation observation) {
if ("OLD".equals(observation.candidatePrediction)) { if ("OLD".equals(observation.candidateRulePrediction)) {
return "OLD"; return "OLD";
} }
return observation.fastestBvh.variant; return observation.fastestBvh.variant;
} }
private static String bestMillisLabel(BvhHeuristicTimingSupport.Observation observation) { private static String bestMillisLabel(BvhHeuristicTimingSupport.Observation observation) {
if ("OLD".equals(observation.candidatePrediction)) { if ("OLD".equals(observation.candidateRulePrediction)) {
return "-"; return "-";
} }
return String.format("%.3f", observation.fastestBvh.averageMillis()); return String.format("%.3f", observation.fastestBvh.averageMillis());
} }
private static String predictedMillisLabel(BvhHeuristicTimingSupport.Observation observation) { private static String predictedMillisLabel(BvhHeuristicTimingSupport.Observation observation) {
if (observation.predictedBvh == null) { if (observation.candidateRuleMeasurement == null) {
return "-"; return "-";
} }
return String.format("%.3f", observation.predictedBvh.averageMillis()); return String.format("%.3f", observation.candidateRuleMeasurement.averageMillis());
} }
private static String lossMillisLabel(BvhHeuristicTimingSupport.Observation observation) { private static String regretMillisLabel(BvhHeuristicTimingSupport.Observation observation) {
if (observation.predictedBvh == null) { if (observation.candidateRuleMeasurement == null) {
return "-"; return "-";
} }
double lossMillis = observation.predictedBvh.averageMillis() - observation.fastestBvh.averageMillis(); return String.format("%.3f", observation.candidateRegretMillis());
return String.format("%.3f", lossMillis); }
private static String regretRatioLabel(BvhHeuristicTimingSupport.Observation observation) {
if (!Double.isFinite(observation.candidateRegretRatio())) {
return "-";
}
return String.format("%.1f%%", observation.candidateRegretRatio() * 100.0);
}
private static String slowdownLabel(BvhHeuristicTimingSupport.Observation observation) {
if (!Double.isFinite(observation.candidateSlowdownFactor())) {
return "-";
}
return String.format("%.2fx", observation.candidateSlowdownFactor());
} }
private static void printAggregateSummary(List<RealObservation> observations) { 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();
System.out.println("[REAL-CITYGML-BVH-HEURISTIC-SUMMARY]"); System.out.println("[REAL-CITYGML-BVH-HEURISTIC-SUMMARY]");
System.out.println(String.format( System.out.println(String.format(
...@@ -333,10 +418,10 @@ public class RealCityGmlBvhHeuristicExplorationTest { ...@@ -333,10 +418,10 @@ public class RealCityGmlBvhHeuristicExplorationTest {
private static void printCheckSummary(String checkName, List<RealObservation> observations) { private static void printCheckSummary(String checkName, List<RealObservation> observations) {
long tested = observations.stream() long tested = observations.stream()
.filter(observation -> !"OLD".equals(observation.observation.candidatePrediction)) .filter(observation -> !"OLD".equals(observation.observation.candidateRulePrediction))
.count(); .count();
long matches = observations.stream() long matches = observations.stream()
.filter(observation -> "yes".equals(observation.observation.candidatePredictionMatchLabel())) .filter(observation -> "yes".equals(observation.observation.candidateRuleMatchLabel()))
.count(); .count();
String accuracy = tested == 0L ? "n/a" : String.format("%.1f%%", 100.0 * matches / tested); String accuracy = tested == 0L ? "n/a" : String.format("%.1f%%", 100.0 * matches / tested);
System.out.println(String.format( System.out.println(String.format(
...@@ -402,7 +487,7 @@ public class RealCityGmlBvhHeuristicExplorationTest { ...@@ -402,7 +487,7 @@ public class RealCityGmlBvhHeuristicExplorationTest {
private static void printSeparator() { private static void printSeparator() {
printSeparator(String.format( printSeparator(String.format(
TABLE_FORMAT, TABLE_FORMAT,
"", "", "", "", "", "", "", "", "", "", "", "", "").length()); "", "", "", "", "", "", "", "", "", "", "", "", "", "", "").length());
} }
private static void printSeparator(int width) { private static void printSeparator(int width) {
...@@ -433,22 +518,40 @@ public class RealCityGmlBvhHeuristicExplorationTest { ...@@ -433,22 +518,40 @@ public class RealCityGmlBvhHeuristicExplorationTest {
private static final class RealObservation { private static final class RealObservation {
final String datasetName; final String datasetName;
final String semanticLabel;
final String dataSplit;
final String fileName; final String fileName;
final String checkName; final String checkName;
final BvhHeuristicTimingSupport.Observation observation; final BvhHeuristicTimingSupport.Observation observation;
RealObservation( RealObservation(
String datasetName, String datasetName,
String semanticLabel,
String dataSplit,
String fileName, String fileName,
String checkName, String checkName,
BvhHeuristicTimingSupport.Observation observation) { BvhHeuristicTimingSupport.Observation observation) {
this.datasetName = datasetName; this.datasetName = datasetName;
this.semanticLabel = semanticLabel;
this.dataSplit = dataSplit;
this.fileName = fileName; this.fileName = fileName;
this.checkName = checkName; this.checkName = checkName;
this.observation = observation; 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 { private interface MetricValue {
double value(RealObservation observation); 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 static org.junit.Assert.assertEquals;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -12,8 +16,17 @@ import de.hft.stuttgart.citydoctor2.check.CheckResult; ...@@ -12,8 +16,17 @@ import de.hft.stuttgart.citydoctor2.check.CheckResult;
import de.hft.stuttgart.citydoctor2.check.ResultStatus; import de.hft.stuttgart.citydoctor2.check.ResultStatus;
import de.hft.stuttgart.citydoctor2.checks.geometry.NestedRingsCheck; import de.hft.stuttgart.citydoctor2.checks.geometry.NestedRingsCheck;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon; 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") @Tag("performance")
public class NestedRingCheckBvhPerformanceTest { public class NestedRingCheckBvhPerformanceTest {
...@@ -28,6 +41,7 @@ public class NestedRingCheckBvhPerformanceTest { ...@@ -28,6 +41,7 @@ public class NestedRingCheckBvhPerformanceTest {
} }
private static void measureScenario(String scenario, ConcretePolygon polygon) { 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(); int ringCount = polygon.getInnerRings().size();
BvhInputMetricsCollector.print(BvhInputMetricsCollector.forRings(scenario, polygon.getInnerRings())); BvhInputMetricsCollector.print(BvhInputMetricsCollector.forRings(scenario, polygon.getInnerRings()));
List<BvhPerformanceTestSupport.Measurement> measurements = new ArrayList<>(); List<BvhPerformanceTestSupport.Measurement> measurements = new ArrayList<>();
...@@ -69,6 +83,7 @@ public class NestedRingCheckBvhPerformanceTest { ...@@ -69,6 +83,7 @@ public class NestedRingCheckBvhPerformanceTest {
} }
private static NestedRingsCheck.Variant variantFor(SplitStrategy strategy) { private static NestedRingsCheck.Variant variantFor(SplitStrategy strategy) {
// The production check exposes concrete BVH strategies through its Variant enum.
switch (strategy) { switch (strategy) {
case BINARY_OBJECT_MEDIAN: case BINARY_OBJECT_MEDIAN:
return NestedRingsCheck.Variant.BVH_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