mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-11 19:14:11 +00:00
Rejection details on surface course diagnostics
Valley solver rejections carry the failing measure (required cut, exposed stations, missing head) through the planner diagnostics; the transect probe prints them.
This commit is contained in:
@@ -926,7 +926,7 @@ final class HydrologyCaveCourseFilter {
|
||||
kind,
|
||||
type,
|
||||
representative.start(),
|
||||
rejection
|
||||
rejection, 0
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1035,7 +1035,7 @@ final class HydrologyCaveCourseFilter {
|
||||
kind,
|
||||
type,
|
||||
representative.start(),
|
||||
HydrologyCandidateRejection.CAVE_CONTAINMENT
|
||||
HydrologyCandidateRejection.CAVE_CONTAINMENT, 0
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -7,7 +7,8 @@ public record HydrologyDiagnosticCandidate(
|
||||
HydrologyCandidateKind kind,
|
||||
HydrologyFeatureType projectedType,
|
||||
HydrologyPoint point,
|
||||
HydrologyCandidateRejection rejection
|
||||
HydrologyCandidateRejection rejection,
|
||||
int detail
|
||||
) {
|
||||
public HydrologyDiagnosticCandidate {
|
||||
Objects.requireNonNull(kind, "kind");
|
||||
|
||||
@@ -1925,7 +1925,7 @@ public final class HydrologyPlanner {
|
||||
HydrologyCandidateKind.OUTLET,
|
||||
candidate.outlet().type(),
|
||||
candidate.outlet().landwardPoint(),
|
||||
HydrologyCandidateRejection.OUTLET_LIMIT
|
||||
HydrologyCandidateRejection.OUTLET_LIMIT, 0
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -2007,7 +2007,10 @@ public final class HydrologyPlanner {
|
||||
);
|
||||
}
|
||||
|
||||
/** Largest rise a surface drainage edge may take: the cut the valley solver will still accept. */
|
||||
/**
|
||||
* Largest rise a surface drainage edge may take. Heads only fall, so a rise along a route becomes a cut
|
||||
* of at least that rise; the lattice may climb no more than the cut the valley solver still accepts.
|
||||
*/
|
||||
private int maximumSurfaceEdgeRise() {
|
||||
int permitted = settings.surface().maximumIncision() - settings.surface().banks().inset() - settings.surface().minimumDepth();
|
||||
return Math.max(1, permitted);
|
||||
@@ -2522,7 +2525,7 @@ public final class HydrologyPlanner {
|
||||
HydrologyCandidateKind.SOURCE,
|
||||
surface ? HydrologyFeatureType.SURFACE_POOL : HydrologyFeatureType.UNDERGROUND_POOL,
|
||||
new HydrologyPoint(node.x(), y, node.z()),
|
||||
rejection
|
||||
rejection, 0
|
||||
));
|
||||
}
|
||||
|
||||
@@ -4134,7 +4137,7 @@ public final class HydrologyPlanner {
|
||||
.reversed()
|
||||
.thenComparingLong(SurfaceCourseDraft::courseId));
|
||||
RiverCourse mainCourse = null;
|
||||
HashMap<Long, HydrologyCandidateRejection> mainRejections = new HashMap<>();
|
||||
HashMap<Long, SurfaceCourseBuild> mainRejections = new HashMap<>();
|
||||
for (SurfaceCourseDraft draft : outletDrafts) {
|
||||
SurfaceCourseBuild candidate = buildSurfaceCourse(
|
||||
draft.courseId(),
|
||||
@@ -4143,7 +4146,7 @@ public final class HydrologyPlanner {
|
||||
draft.path()
|
||||
);
|
||||
if (candidate.course() == null) {
|
||||
mainRejections.put(draft.courseId(), candidate.rejection());
|
||||
mainRejections.put(draft.courseId(), candidate);
|
||||
continue;
|
||||
}
|
||||
mainCourse = candidate.course();
|
||||
@@ -4152,11 +4155,13 @@ public final class HydrologyPlanner {
|
||||
}
|
||||
if (mainCourse == null) {
|
||||
for (SurfaceCourseDraft draft : outletDrafts) {
|
||||
SurfaceCourseBuild rejected = mainRejections.get(draft.courseId());
|
||||
addCompiledSourceDiagnostic(
|
||||
draft.source(),
|
||||
true,
|
||||
draft.courseId(),
|
||||
mainRejections.get(draft.courseId()),
|
||||
rejected.rejection(),
|
||||
rejected.rejectionDetail(),
|
||||
diagnostics
|
||||
);
|
||||
}
|
||||
@@ -4180,7 +4185,7 @@ public final class HydrologyPlanner {
|
||||
CoursePath path
|
||||
) {
|
||||
if (path.points().size() < 2) {
|
||||
return SurfaceCourseBuild.rejected(HydrologyCandidateRejection.COURSE_TOO_SHORT);
|
||||
return SurfaceCourseBuild.rejected(HydrologyCandidateRejection.COURSE_TOO_SHORT, 0);
|
||||
}
|
||||
RiverOutlet outlet = path.outlet();
|
||||
boolean surfaceSinkhole = path.reachesOutlet() && surfaceSinkhole(outlet);
|
||||
@@ -4203,7 +4208,7 @@ public final class HydrologyPlanner {
|
||||
terminalHead
|
||||
);
|
||||
if (!result.accepted()) {
|
||||
return SurfaceCourseBuild.rejected(result.rejection());
|
||||
return SurfaceCourseBuild.rejected(result.rejection(), result.rejectionDetail());
|
||||
}
|
||||
ArrayList<HydraulicSegment> segments = new ArrayList<>(result.segments());
|
||||
if (path.reachesOutlet()) {
|
||||
@@ -4226,7 +4231,7 @@ public final class HydrologyPlanner {
|
||||
);
|
||||
}
|
||||
if (path.organicSurfaceRequired() && !surfaceShapeAccepted(path.points())) {
|
||||
return SurfaceCourseBuild.rejected(HydrologyCandidateRejection.SURFACE_SHAPE_UNSUPPORTED);
|
||||
return SurfaceCourseBuild.rejected(HydrologyCandidateRejection.SURFACE_SHAPE_UNSUPPORTED, 0);
|
||||
}
|
||||
int discharge = maximumSurfaceDischarge(path.edges());
|
||||
return SurfaceCourseBuild.accepted(new RiverCourse(
|
||||
@@ -4458,6 +4463,17 @@ public final class HydrologyPlanner {
|
||||
long courseId,
|
||||
HydrologyCandidateRejection rejection,
|
||||
List<HydrologyDiagnosticCandidate> diagnostics
|
||||
) {
|
||||
addCompiledSourceDiagnostic(source, surface, courseId, rejection, 0, diagnostics);
|
||||
}
|
||||
|
||||
private void addCompiledSourceDiagnostic(
|
||||
GridNode source,
|
||||
boolean surface,
|
||||
long courseId,
|
||||
HydrologyCandidateRejection rejection,
|
||||
int detail,
|
||||
List<HydrologyDiagnosticCandidate> diagnostics
|
||||
) {
|
||||
int y = surface ? source.terrain().naturalHeight() : source.terrain().caveFluidY();
|
||||
diagnostics.add(new HydrologyDiagnosticCandidate(
|
||||
@@ -4465,7 +4481,8 @@ public final class HydrologyPlanner {
|
||||
HydrologyCandidateKind.SOURCE,
|
||||
surface ? HydrologyFeatureType.SURFACE_POOL : HydrologyFeatureType.UNDERGROUND_POOL,
|
||||
new HydrologyPoint(source.x(), y, source.z()),
|
||||
rejection
|
||||
rejection,
|
||||
detail
|
||||
));
|
||||
}
|
||||
|
||||
@@ -6244,7 +6261,7 @@ public final class HydrologyPlanner {
|
||||
HydrologyCandidateKind.DEEP_FLUID,
|
||||
deepFluid.containedPools() ? HydrologyFeatureType.DEEP_POOL : HydrologyFeatureType.DEEP_CHANNEL,
|
||||
new HydrologyPoint(site.x(), site.head(), site.z()),
|
||||
rejection
|
||||
rejection, 0
|
||||
));
|
||||
}
|
||||
|
||||
@@ -7259,7 +7276,7 @@ public final class HydrologyPlanner {
|
||||
HydrologyCandidateKind.SOURCE,
|
||||
HydrologyFeatureType.SURFACE_POOL,
|
||||
representative.start(),
|
||||
HydrologyCandidateRejection.SOURCE_SPACING
|
||||
HydrologyCandidateRejection.SOURCE_SPACING, 0
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7385,7 +7402,8 @@ public final class HydrologyPlanner {
|
||||
|
||||
private record SurfaceCourseBuild(
|
||||
RiverCourse course,
|
||||
HydrologyCandidateRejection rejection
|
||||
HydrologyCandidateRejection rejection,
|
||||
int rejectionDetail
|
||||
) {
|
||||
private SurfaceCourseBuild {
|
||||
if ((course == null) == (rejection == null)) {
|
||||
@@ -7394,11 +7412,11 @@ public final class HydrologyPlanner {
|
||||
}
|
||||
|
||||
private static SurfaceCourseBuild accepted(RiverCourse course) {
|
||||
return new SurfaceCourseBuild(Objects.requireNonNull(course, "course"), null);
|
||||
return new SurfaceCourseBuild(Objects.requireNonNull(course, "course"), null, 0);
|
||||
}
|
||||
|
||||
private static SurfaceCourseBuild rejected(HydrologyCandidateRejection rejection) {
|
||||
return new SurfaceCourseBuild(null, Objects.requireNonNull(rejection, "rejection"));
|
||||
private static SurfaceCourseBuild rejected(HydrologyCandidateRejection rejection, int detail) {
|
||||
return new SurfaceCourseBuild(null, Objects.requireNonNull(rejection, "rejection"), detail);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -46,7 +46,7 @@ public final class SurfaceCourseBuilder {
|
||||
ValleyProfile valley = new ValleyProfileSolver(surface, sampler, seaLevel, minimumCourseLength)
|
||||
.solve(centerline, channel, terminal, terminalHead);
|
||||
if (!valley.accepted()) {
|
||||
return SurfaceCourseResult.rejected(valley.rejection());
|
||||
return SurfaceCourseResult.rejected(valley.rejection(), valley.rejectionDetail());
|
||||
}
|
||||
int exposed = valley.exposedStations();
|
||||
boolean coastalDrop = directOcean && valley.head()[exposed - 1] > seaLevel;
|
||||
@@ -82,7 +82,7 @@ public final class SurfaceCourseBuilder {
|
||||
List<HydraulicSegment> segments = SurfaceSegmentLabeler.label(
|
||||
worldSeed, courseId, x, z, head, width, depth, surface.banks());
|
||||
if (segments.isEmpty()) {
|
||||
return SurfaceCourseResult.rejected(HydrologyCandidateRejection.COURSE_TOO_SHORT);
|
||||
return SurfaceCourseResult.rejected(HydrologyCandidateRejection.COURSE_TOO_SHORT, stations);
|
||||
}
|
||||
HydraulicSegment last = segments.getLast();
|
||||
int lastStation = stations - 1;
|
||||
@@ -92,7 +92,8 @@ public final class SurfaceCourseBuilder {
|
||||
last.width(),
|
||||
last.depth(),
|
||||
new HydrologyPoint(x[lastStation], head[lastStation], z[lastStation]),
|
||||
null
|
||||
null,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -13,7 +13,8 @@ public record SurfaceCourseResult(
|
||||
int lastWidth,
|
||||
int lastDepth,
|
||||
HydrologyPoint pathEnd,
|
||||
HydrologyCandidateRejection rejection
|
||||
HydrologyCandidateRejection rejection,
|
||||
int rejectionDetail
|
||||
) {
|
||||
public SurfaceCourseResult {
|
||||
segments = List.copyOf(Objects.requireNonNull(segments, "segments"));
|
||||
@@ -23,7 +24,7 @@ public record SurfaceCourseResult(
|
||||
return rejection == null;
|
||||
}
|
||||
|
||||
public static SurfaceCourseResult rejected(HydrologyCandidateRejection rejection) {
|
||||
return new SurfaceCourseResult(List.of(), 0, 0, 0, null, Objects.requireNonNull(rejection, "rejection"));
|
||||
public static SurfaceCourseResult rejected(HydrologyCandidateRejection rejection, int detail) {
|
||||
return new SurfaceCourseResult(List.of(), 0, 0, 0, null, Objects.requireNonNull(rejection, "rejection"), detail);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,17 +8,19 @@ public record ValleyProfile(
|
||||
int[] crossMax,
|
||||
int[] centerNatural,
|
||||
int exposedStations,
|
||||
HydrologyCandidateRejection rejection
|
||||
HydrologyCandidateRejection rejection,
|
||||
int rejectionDetail
|
||||
) {
|
||||
public boolean accepted() {
|
||||
return rejection == null;
|
||||
}
|
||||
|
||||
public static ValleyProfile fromHeads(int[] head, int exposedStations) {
|
||||
return new ValleyProfile(head, new int[0], new int[0], new int[0], exposedStations, null);
|
||||
return new ValleyProfile(head, new int[0], new int[0], new int[0], exposedStations, null, 0);
|
||||
}
|
||||
|
||||
public static ValleyProfile rejected(HydrologyCandidateRejection rejection) {
|
||||
return new ValleyProfile(new int[0], new int[0], new int[0], new int[0], 0, rejection);
|
||||
/** {@code detail} names the measure that failed: exposed stations, required cut, or missing head. */
|
||||
public static ValleyProfile rejected(HydrologyCandidateRejection rejection, int detail) {
|
||||
return new ValleyProfile(new int[0], new int[0], new int[0], new int[0], 0, rejection, detail);
|
||||
}
|
||||
}
|
||||
|
||||
+13
-8
@@ -79,10 +79,10 @@ public final class ValleyProfileSolver {
|
||||
crossMax[station] = maximum;
|
||||
}
|
||||
if (exposed < 2 || exposed < minimumCourseLength) {
|
||||
return ValleyProfile.rejected(HydrologyCandidateRejection.COURSE_TOO_SHORT);
|
||||
return ValleyProfile.rejected(HydrologyCandidateRejection.COURSE_TOO_SHORT, exposed);
|
||||
}
|
||||
if (exposed < count && terminal != SurfaceTerminal.OCEAN_MOUTH) {
|
||||
return ValleyProfile.rejected(HydrologyCandidateRejection.SURFACE_EXPOSURE);
|
||||
return ValleyProfile.rejected(HydrologyCandidateRejection.SURFACE_EXPOSURE, exposed);
|
||||
}
|
||||
int inset = surface.banks().inset();
|
||||
int[] head = new int[count];
|
||||
@@ -101,19 +101,24 @@ public final class ValleyProfileSolver {
|
||||
}
|
||||
}
|
||||
if (terminal == SurfaceTerminal.SINKHOLE && head[exposed - 1] < terminalHead) {
|
||||
return ValleyProfile.rejected(HydrologyCandidateRejection.SURFACE_SINKHOLE_CLEARANCE);
|
||||
return ValleyProfile.rejected(HydrologyCandidateRejection.SURFACE_SINKHOLE_CLEARANCE, terminalHead - head[exposed - 1]);
|
||||
}
|
||||
if (terminal == SurfaceTerminal.COASTAL_GROTTO && head[exposed - 1] < terminalHead) {
|
||||
return ValleyProfile.rejected(HydrologyCandidateRejection.SURFACE_HEAD_RANGE);
|
||||
return ValleyProfile.rejected(HydrologyCandidateRejection.SURFACE_HEAD_RANGE, terminalHead - head[exposed - 1]);
|
||||
}
|
||||
int maximumIncision = surface.maximumIncision();
|
||||
int deepestCut = 0;
|
||||
boolean rejected = false;
|
||||
for (int station = 0; station < exposed; station++) {
|
||||
int permitted = Math.min(maximumIncision, (int) StrictMath.floor(maximumIncision * incisionMultiplier[station]));
|
||||
int bed = head[station] - (int) StrictMath.round(channel.depth()[station]);
|
||||
if (centerNatural[station] - bed > permitted) {
|
||||
return ValleyProfile.rejected(HydrologyCandidateRejection.SURFACE_CORRIDOR_UNSUPPORTED);
|
||||
}
|
||||
int cut = centerNatural[station] - bed;
|
||||
deepestCut = Math.max(deepestCut, cut);
|
||||
rejected |= cut > permitted;
|
||||
}
|
||||
return new ValleyProfile(head, crossMin, crossMax, centerNatural, exposed, null);
|
||||
if (rejected) {
|
||||
return ValleyProfile.rejected(HydrologyCandidateRejection.SURFACE_CORRIDOR_UNSUPPORTED, deepestCut);
|
||||
}
|
||||
return new ValleyProfile(head, crossMin, crossMax, centerNatural, exposed, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@ public class IrisRendererRiverTest {
|
||||
HydrologyCandidateKind.SOURCE,
|
||||
HydrologyFeatureType.SURFACE_POOL,
|
||||
new HydrologyPoint(0, 80, 0),
|
||||
HydrologyCandidateRejection.NO_LEGAL_OUTLET
|
||||
HydrologyCandidateRejection.NO_LEGAL_OUTLET, 0
|
||||
);
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(complex.getHydrologyRuntime()).thenReturn(runtime);
|
||||
|
||||
@@ -219,13 +219,23 @@ public final class RiverTransectProbe {
|
||||
|
||||
static List<String> rejectionLines(HydrologyTile tile) {
|
||||
TreeMap<String, Integer> counts = new TreeMap<>();
|
||||
TreeMap<String, ArrayList<Integer>> details = new TreeMap<>();
|
||||
for (HydrologyDiagnosticCandidate candidate : tile.diagnosticCandidates()) {
|
||||
String key = "rejected " + candidate.kind() + " " + candidate.projectedType() + " " + candidate.rejection();
|
||||
counts.merge(key, 1, Integer::sum);
|
||||
if (candidate.detail() != 0) {
|
||||
details.computeIfAbsent(key, (String ignored) -> new ArrayList<>()).add(candidate.detail());
|
||||
}
|
||||
}
|
||||
ArrayList<String> lines = new ArrayList<>(counts.size());
|
||||
for (Map.Entry<String, Integer> entry : counts.entrySet()) {
|
||||
lines.add(entry.getKey() + " x" + entry.getValue());
|
||||
ArrayList<Integer> values = details.get(entry.getKey());
|
||||
String suffix = "";
|
||||
if (values != null) {
|
||||
values.sort(null);
|
||||
suffix = " detail=" + values.subList(0, Math.min(values.size(), 24));
|
||||
}
|
||||
lines.add(entry.getKey() + " x" + entry.getValue() + suffix);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user