Fix: Distinguish concrete ray failure causes
This commit is contained in:
1 parent
76ed705d37
commit
95c05f84d9
12 files changed
+580
-111
No files matched your search
@@ -790,6 +790,15 @@ ray-tracing 的 OpenMP worker 与 geodesic 热循环不写任何日志(既有
|
|||||||
报告,与数值 `INCOMPLETE` 区分;replay 使用文件中保存的几何与顶点诊断,不虚构
|
报告,与数值 `INCOMPLETE` 区分;replay 使用文件中保存的几何与顶点诊断,不虚构
|
||||||
stop 状态,也不把未重试的旧失败当作新失败重复计数。
|
stop 状态,也不把未重试的旧失败当作新失败重复计数。
|
||||||
|
|
||||||
|
`RayReason` 的诊断粒度是追加式扩展:原 coarse reason 的数值 0..9 冻结不变,
|
||||||
|
更细的失败原因一律追加在 `RAY_REASON_IO_ERROR` 之后,`RAY_REASON_COUNT` 只作
|
||||||
|
计数/未知 sentinel 且不写入 wire。lens-map 的二进制布局(v2/v3)与字段偏移不
|
||||||
|
改变,新 reader 读旧值原样、读追加值按新名解释,并拒绝 `>= RAY_REASON_COUNT`;
|
||||||
|
旧 reader 因 coarse 校验仍会安全拒绝它不认识的新值。每个细原因通过纯函数
|
||||||
|
`ray_reason_category()` 归入既有 coarse 类别(PROTOCOL_ERROR、
|
||||||
|
INTEGRATION_ERROR、UNSUPPORTED、IO_ERROR 等),供只需要粗分类的调用方使用。
|
||||||
|
该扩展只细化失败状态的命名与上报,不改变任何物理终态判据、终止分类或 retry 规则。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 18A. 渐近外区、escape worldtube 与 endpoint 协议
|
# 18A. 渐近外区、escape worldtube 与 endpoint 协议
|
||||||
|
|||||||
+1
-1
@@ -1661,7 +1661,7 @@ int frame_lens_mesh_refine_with_progress(
|
|||||||
endpoint = (RayEndpoint){.magnification = 1.0,
|
endpoint = (RayEndpoint){.magnification = 1.0,
|
||||||
.end_id = SPACETIME_END_NONE,
|
.end_id = SPACETIME_END_NONE,
|
||||||
.outcome = RAY_OUTCOME_INCOMPLETE,
|
.outcome = RAY_OUTCOME_INCOMPLETE,
|
||||||
.reason = RAY_REASON_PROTOCOL_ERROR,
|
.reason = RAY_REASON_INVALID_CONTINUATION,
|
||||||
.stop_coordinate_time = NAN,
|
.stop_coordinate_time = NAN,
|
||||||
.threshold_value = NAN};
|
.threshold_value = NAN};
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+220
-54
@@ -31,10 +31,120 @@ const char *ray_reason_name(RayReason reason) {
|
|||||||
return "PROTOCOL_ERROR";
|
return "PROTOCOL_ERROR";
|
||||||
case RAY_REASON_IO_ERROR:
|
case RAY_REASON_IO_ERROR:
|
||||||
return "IO_ERROR";
|
return "IO_ERROR";
|
||||||
|
case RAY_REASON_INVALID_ARGUMENT:
|
||||||
|
return "INVALID_ARGUMENT";
|
||||||
|
case RAY_REASON_UNKNOWN_STEPPER:
|
||||||
|
return "UNKNOWN_STEPPER";
|
||||||
|
case RAY_REASON_INVALID_STEPPER_CONFIG:
|
||||||
|
return "INVALID_STEPPER_CONFIG";
|
||||||
|
case RAY_REASON_ASYMPTOTIC_LIFECYCLE_INVALID:
|
||||||
|
return "ASYMPTOTIC_LIFECYCLE_INVALID";
|
||||||
|
case RAY_REASON_CAMERA_PREROUTE_FAILED:
|
||||||
|
return "CAMERA_PREROUTE_FAILED";
|
||||||
|
case RAY_REASON_INVALID_ROUTE_KIND:
|
||||||
|
return "INVALID_ROUTE_KIND";
|
||||||
|
case RAY_REASON_INVALID_CONTINUATION:
|
||||||
|
return "INVALID_CONTINUATION";
|
||||||
|
case RAY_REASON_METRIC_INTERNAL_ERROR:
|
||||||
|
return "METRIC_INTERNAL_ERROR";
|
||||||
|
case RAY_REASON_END_DESCRIPTOR_FAILED:
|
||||||
|
return "END_DESCRIPTOR_FAILED";
|
||||||
|
case RAY_REASON_WORLDTUBE_SAMPLE_FAILED:
|
||||||
|
return "WORLDTUBE_SAMPLE_FAILED";
|
||||||
|
case RAY_REASON_WORLDTUBE_GEOMETRY_INVALID:
|
||||||
|
return "WORLDTUBE_GEOMETRY_INVALID";
|
||||||
|
case RAY_REASON_WORLDTUBE_EVALUATION_FAILED:
|
||||||
|
return "WORLDTUBE_EVALUATION_FAILED";
|
||||||
|
case RAY_REASON_OUTSIDE_WORLDTUBE:
|
||||||
|
return "OUTSIDE_WORLDTUBE";
|
||||||
|
case RAY_REASON_ESCAPE_LOCALIZATION_FAILED:
|
||||||
|
return "ESCAPE_LOCALIZATION_FAILED";
|
||||||
|
case RAY_REASON_ESCAPE_TRANSFER_FAILED:
|
||||||
|
return "ESCAPE_TRANSFER_FAILED";
|
||||||
|
case RAY_REASON_INVALID_ESCAPE_DIRECTION:
|
||||||
|
return "INVALID_ESCAPE_DIRECTION";
|
||||||
|
case RAY_REASON_INVALID_STEP_INTERVAL:
|
||||||
|
return "INVALID_STEP_INTERVAL";
|
||||||
|
case RAY_REASON_TIME_STEP_UNREPRESENTABLE:
|
||||||
|
return "TIME_STEP_UNREPRESENTABLE";
|
||||||
|
case RAY_REASON_MIN_STEP_REACHED:
|
||||||
|
return "MIN_STEP_REACHED";
|
||||||
|
case RAY_REASON_REJECTION_LIMIT:
|
||||||
|
return "REJECTION_LIMIT";
|
||||||
|
case RAY_REASON_NONFINITE_TRIAL:
|
||||||
|
return "NONFINITE_TRIAL";
|
||||||
|
case RAY_REASON_SUBINTEGRATION_TARGET_INVALID:
|
||||||
|
return "SUBINTEGRATION_TARGET_INVALID";
|
||||||
|
case RAY_REASON_SUBINTEGRATION_TARGET_MISSED:
|
||||||
|
return "SUBINTEGRATION_TARGET_MISSED";
|
||||||
|
case RAY_REASON_ESCAPE_EVENT_UNCONFIRMED:
|
||||||
|
return "ESCAPE_EVENT_UNCONFIRMED";
|
||||||
|
case RAY_REASON_THRESHOLD_EVENT_UNCONFIRMED:
|
||||||
|
return "THRESHOLD_EVENT_UNCONFIRMED";
|
||||||
|
case RAY_REASON_SLAB_LOAD_FAILED:
|
||||||
|
return "SLAB_LOAD_FAILED";
|
||||||
|
case RAY_REASON_COUNT:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ray_reason_valid(RayReason reason) {
|
||||||
|
return (unsigned)reason < (unsigned)RAY_REASON_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
RayReason ray_reason_category(RayReason reason) {
|
||||||
|
switch (reason) {
|
||||||
|
/* Frozen coarse reasons map to themselves. */
|
||||||
|
case RAY_REASON_NONE:
|
||||||
|
case RAY_REASON_REDSHIFT_LIMIT:
|
||||||
|
case RAY_REASON_BUDGET_EXHAUSTED:
|
||||||
|
case RAY_REASON_TIME_RANGE_EXHAUSTED:
|
||||||
|
case RAY_REASON_OUT_OF_DOMAIN:
|
||||||
|
case RAY_REASON_INVALID_METRIC:
|
||||||
|
case RAY_REASON_INTEGRATION_ERROR:
|
||||||
|
case RAY_REASON_UNSUPPORTED:
|
||||||
|
case RAY_REASON_PROTOCOL_ERROR:
|
||||||
|
case RAY_REASON_IO_ERROR:
|
||||||
|
return reason;
|
||||||
|
/* Protocol-derived detail reasons collapse onto PROTOCOL_ERROR. */
|
||||||
|
case RAY_REASON_INVALID_ARGUMENT:
|
||||||
|
case RAY_REASON_UNKNOWN_STEPPER:
|
||||||
|
case RAY_REASON_INVALID_STEPPER_CONFIG:
|
||||||
|
case RAY_REASON_ASYMPTOTIC_LIFECYCLE_INVALID:
|
||||||
|
case RAY_REASON_CAMERA_PREROUTE_FAILED:
|
||||||
|
case RAY_REASON_INVALID_ROUTE_KIND:
|
||||||
|
case RAY_REASON_INVALID_CONTINUATION:
|
||||||
|
case RAY_REASON_METRIC_INTERNAL_ERROR:
|
||||||
|
case RAY_REASON_END_DESCRIPTOR_FAILED:
|
||||||
|
case RAY_REASON_WORLDTUBE_SAMPLE_FAILED:
|
||||||
|
case RAY_REASON_WORLDTUBE_GEOMETRY_INVALID:
|
||||||
|
case RAY_REASON_WORLDTUBE_EVALUATION_FAILED:
|
||||||
|
case RAY_REASON_OUTSIDE_WORLDTUBE:
|
||||||
|
case RAY_REASON_ESCAPE_LOCALIZATION_FAILED:
|
||||||
|
case RAY_REASON_ESCAPE_TRANSFER_FAILED:
|
||||||
|
return RAY_REASON_PROTOCOL_ERROR;
|
||||||
|
/* Integration-derived reasons, plus the legacy sky-direction failure that
|
||||||
|
* historically surfaced as an integration error. */
|
||||||
|
case RAY_REASON_INVALID_ESCAPE_DIRECTION:
|
||||||
|
case RAY_REASON_INVALID_STEP_INTERVAL:
|
||||||
|
case RAY_REASON_TIME_STEP_UNREPRESENTABLE:
|
||||||
|
case RAY_REASON_MIN_STEP_REACHED:
|
||||||
|
case RAY_REASON_REJECTION_LIMIT:
|
||||||
|
case RAY_REASON_NONFINITE_TRIAL:
|
||||||
|
case RAY_REASON_SUBINTEGRATION_TARGET_INVALID:
|
||||||
|
case RAY_REASON_SUBINTEGRATION_TARGET_MISSED:
|
||||||
|
case RAY_REASON_ESCAPE_EVENT_UNCONFIRMED:
|
||||||
|
case RAY_REASON_THRESHOLD_EVENT_UNCONFIRMED:
|
||||||
|
return RAY_REASON_INTEGRATION_ERROR;
|
||||||
|
case RAY_REASON_SLAB_LOAD_FAILED:
|
||||||
|
return RAY_REASON_IO_ERROR;
|
||||||
|
case RAY_REASON_COUNT:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (RayReason)RAY_REASON_COUNT; /* documented UNKNOWN sentinel */
|
||||||
|
}
|
||||||
|
|
||||||
static double dot(const double a[3], const double b[3]) {
|
static double dot(const double a[3], const double b[3]) {
|
||||||
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
||||||
}
|
}
|
||||||
@@ -477,25 +587,26 @@ static int dp_advance_one(const MetricSlab *slab,
|
|||||||
const double t0 = s->coordinate_time;
|
const double t0 = s->coordinate_time;
|
||||||
const double remaining = t0 - left;
|
const double remaining = t0 - left;
|
||||||
if (!(remaining > 0.0) || !(proposal > 0.0)) {
|
if (!(remaining > 0.0) || !(proposal > 0.0)) {
|
||||||
*reason = RAY_REASON_INTEGRATION_ERROR;
|
*reason = RAY_REASON_INVALID_STEP_INTERVAL;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
const int boundary_limited = remaining < proposal;
|
const int boundary_limited = remaining < proposal;
|
||||||
double target = boundary_limited ? left : representable_target(t0, proposal);
|
double target = boundary_limited ? left : representable_target(t0, proposal);
|
||||||
if (!(target < t0)) {
|
if (!(target < t0)) {
|
||||||
*reason = RAY_REASON_INTEGRATION_ERROR;
|
*reason = RAY_REASON_TIME_STEP_UNREPRESENTABLE;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
double actual_h = t0 - target;
|
double actual_h = t0 - target;
|
||||||
/* A boundary-limited final step may legitimately fall below min_step; any
|
/* A boundary-limited final step may legitimately fall below min_step; any
|
||||||
* other sub-floor representable interval is an integration failure. */
|
* other sub-floor representable interval is an integration failure. */
|
||||||
if (!boundary_limited && actual_h < config->min_step) {
|
if (!boundary_limited && actual_h < config->min_step) {
|
||||||
*reason = RAY_REASON_INTEGRATION_ERROR;
|
*reason = RAY_REASON_MIN_STEP_REACHED;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
unsigned int consecutive = 0;
|
unsigned int consecutive = 0;
|
||||||
int rejected_any = 0;
|
int rejected_any = 0;
|
||||||
int last_reject_stage = 0;
|
int last_reject_stage = 0;
|
||||||
|
int last_reject_nonfinite = 0;
|
||||||
SpacetimePointStatus last_stage_status = SPACETIME_POINT_OK;
|
SpacetimePointStatus last_stage_status = SPACETIME_POINT_OK;
|
||||||
while (1) {
|
while (1) {
|
||||||
const State before = *s;
|
const State before = *s;
|
||||||
@@ -559,9 +670,17 @@ static int dp_advance_one(const MetricSlab *slab,
|
|||||||
rejected_any = 1;
|
rejected_any = 1;
|
||||||
s->previous_rejected = 1;
|
s->previous_rejected = 1;
|
||||||
last_reject_stage = stage_reject;
|
last_reject_stage = stage_reject;
|
||||||
|
last_reject_nonfinite = (status == DP_TRIAL_NONFINITE);
|
||||||
if (++consecutive >= config->consecutive_rejection_limit) {
|
if (++consecutive >= config->consecutive_rejection_limit) {
|
||||||
*reason = last_reject_stage ? reason_from_point_status(last_stage_status)
|
/* A specific stage reason is more informative than the quota itself and
|
||||||
: RAY_REASON_INTEGRATION_ERROR;
|
* is preserved; only a pure error-estimate rejection (or a nonfinite
|
||||||
|
* trial) reports the quota cause. */
|
||||||
|
if (last_reject_stage)
|
||||||
|
*reason = reason_from_point_status(last_stage_status);
|
||||||
|
else if (last_reject_nonfinite)
|
||||||
|
*reason = RAY_REASON_NONFINITE_TRIAL;
|
||||||
|
else
|
||||||
|
*reason = RAY_REASON_REJECTION_LIMIT;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (factor > 1.0)
|
if (factor > 1.0)
|
||||||
@@ -571,12 +690,12 @@ static int dp_advance_one(const MetricSlab *slab,
|
|||||||
shrunk = remaining;
|
shrunk = remaining;
|
||||||
target = (shrunk >= remaining) ? left : representable_target(t0, shrunk);
|
target = (shrunk >= remaining) ? left : representable_target(t0, shrunk);
|
||||||
if (!(target < t0)) {
|
if (!(target < t0)) {
|
||||||
*reason = RAY_REASON_INTEGRATION_ERROR;
|
*reason = RAY_REASON_TIME_STEP_UNREPRESENTABLE;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
actual_h = t0 - target;
|
actual_h = t0 - target;
|
||||||
if (actual_h < config->min_step) {
|
if (actual_h < config->min_step) {
|
||||||
*reason = RAY_REASON_INTEGRATION_ERROR;
|
*reason = RAY_REASON_MIN_STEP_REACHED;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -653,17 +772,23 @@ typedef enum {
|
|||||||
ASYM_LIFECYCLE_PROTOCOL_ERROR
|
ASYM_LIFECYCLE_PROTOCOL_ERROR
|
||||||
} AsymLifecycleMode;
|
} AsymLifecycleMode;
|
||||||
|
|
||||||
static AsymLifecycleMode asym_lifecycle_mode(const SpacetimeSource *source) {
|
static AsymLifecycleMode asym_lifecycle_mode(const SpacetimeSource *source,
|
||||||
|
RayReason *failure) {
|
||||||
|
*failure = RAY_REASON_ASYMPTOTIC_LIFECYCLE_INVALID;
|
||||||
const size_t count = spacetime_asymptotic_end_count(source);
|
const size_t count = spacetime_asymptotic_end_count(source);
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
return ASYM_LIFECYCLE_NONE;
|
return ASYM_LIFECYCLE_NONE;
|
||||||
for (size_t i = 0; i < count; ++i) {
|
for (size_t i = 0; i < count; ++i) {
|
||||||
SpacetimeAsymptoticEnd end;
|
SpacetimeAsymptoticEnd end;
|
||||||
if (spacetime_asymptotic_end(source, i, &end))
|
if (spacetime_asymptotic_end(source, i, &end)) {
|
||||||
|
*failure = RAY_REASON_END_DESCRIPTOR_FAILED;
|
||||||
return ASYM_LIFECYCLE_PROTOCOL_ERROR;
|
return ASYM_LIFECYCLE_PROTOCOL_ERROR;
|
||||||
|
}
|
||||||
if (end.exterior_kind != ASYMPTOTIC_EXTERIOR_MINKOWSKI &&
|
if (end.exterior_kind != ASYMPTOTIC_EXTERIOR_MINKOWSKI &&
|
||||||
end.exterior_kind != ASYMPTOTIC_EXTERIOR_SCHWARZSCHILD_MONOPOLE)
|
end.exterior_kind != ASYMPTOTIC_EXTERIOR_SCHWARZSCHILD_MONOPOLE) {
|
||||||
|
*failure = RAY_REASON_ASYMPTOTIC_LIFECYCLE_INVALID;
|
||||||
return ASYM_LIFECYCLE_PROTOCOL_ERROR;
|
return ASYM_LIFECYCLE_PROTOCOL_ERROR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ASYM_LIFECYCLE_READY;
|
return ASYM_LIFECYCLE_READY;
|
||||||
}
|
}
|
||||||
@@ -672,12 +797,18 @@ static AsymLifecycleMode asym_lifecycle_mode(const SpacetimeSource *source) {
|
|||||||
* within one accepted step. Re-integrates from `before` with fractional step
|
* within one accepted step. Re-integrates from `before` with fractional step
|
||||||
* sizes; `after` lands on the outside end of the bracket. Returns
|
* sizes; `after` lands on the outside end of the bracket. Returns
|
||||||
* ASYMPTOTIC_OK, ASYMPTOTIC_TIME_RANGE_EXHAUSTED (a midpoint fell into a
|
* ASYMPTOTIC_OK, ASYMPTOTIC_TIME_RANGE_EXHAUSTED (a midpoint fell into a
|
||||||
* history hole), or ASYMPTOTIC_INVALID. */
|
* history hole), or ASYMPTOTIC_INVALID. On a non-TIME_RANGE failure
|
||||||
|
* `*fail_reason` carries the classified cause: an RK4 subintegration failure
|
||||||
|
* (whose underlying metric reason is discarded by this collapsed path) is
|
||||||
|
* ESCAPE_LOCALIZATION_FAILED, while a worldtube value failure is
|
||||||
|
* WORLDTUBE_EVALUATION_FAILED. */
|
||||||
static AsymptoticStatus localize_worldtube_crossing(const MetricSlab *slab,
|
static AsymptoticStatus localize_worldtube_crossing(const MetricSlab *slab,
|
||||||
SpacetimeEndId end_id,
|
SpacetimeEndId end_id,
|
||||||
const State *before,
|
const State *before,
|
||||||
double h, State *after,
|
double h, State *after,
|
||||||
|
RayReason *fail_reason,
|
||||||
unsigned long *rhs_total) {
|
unsigned long *rhs_total) {
|
||||||
|
*fail_reason = RAY_REASON_ESCAPE_LOCALIZATION_FAILED;
|
||||||
double f_lo = 0.0, f_hi = 1.0;
|
double f_lo = 0.0, f_hi = 1.0;
|
||||||
for (int iteration = 0; iteration < 64; ++iteration) {
|
for (int iteration = 0; iteration < 64; ++iteration) {
|
||||||
const double f = 0.5 * (f_lo + f_hi);
|
const double f = 0.5 * (f_lo + f_hi);
|
||||||
@@ -688,8 +819,11 @@ static AsymptoticStatus localize_worldtube_crossing(const MetricSlab *slab,
|
|||||||
double value;
|
double value;
|
||||||
const int status = asymptotic_worldtube_value(
|
const int status = asymptotic_worldtube_value(
|
||||||
slab->source, end_id, mid.coordinate_time, mid.x, &value);
|
slab->source, end_id, mid.coordinate_time, mid.x, &value);
|
||||||
if (status != ASYMPTOTIC_OK)
|
if (status != ASYMPTOTIC_OK) {
|
||||||
|
if (status != ASYMPTOTIC_TIME_RANGE_EXHAUSTED)
|
||||||
|
*fail_reason = RAY_REASON_WORLDTUBE_EVALUATION_FAILED;
|
||||||
return (AsymptoticStatus)status;
|
return (AsymptoticStatus)status;
|
||||||
|
}
|
||||||
if (value >= 0.0)
|
if (value >= 0.0)
|
||||||
f_hi = f;
|
f_hi = f;
|
||||||
else
|
else
|
||||||
@@ -732,7 +866,7 @@ static int dp_subintegrate(const MetricSlab *slab,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (!(t_target < before->coordinate_time)) {
|
if (!(t_target < before->coordinate_time)) {
|
||||||
*reason_out = RAY_REASON_INTEGRATION_ERROR;
|
*reason_out = RAY_REASON_SUBINTEGRATION_TARGET_INVALID;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
State work = *before;
|
State work = *before;
|
||||||
@@ -755,7 +889,7 @@ static int dp_subintegrate(const MetricSlab *slab,
|
|||||||
work.coordinate_time = t_target;
|
work.coordinate_time = t_target;
|
||||||
}
|
}
|
||||||
if (fabs(work.coordinate_time - t_target) > snap) {
|
if (fabs(work.coordinate_time - t_target) > snap) {
|
||||||
*reason_out = RAY_REASON_INTEGRATION_ERROR;
|
*reason_out = RAY_REASON_SUBINTEGRATION_TARGET_MISSED;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
work.coordinate_time = t_target;
|
work.coordinate_time = t_target;
|
||||||
@@ -789,7 +923,7 @@ static AsymptoticStatus dp_localize_worldtube_crossing(
|
|||||||
const double span = t_end - t0;
|
const double span = t_end - t0;
|
||||||
const double stop_tol =
|
const double stop_tol =
|
||||||
fmax(1e-12 * fabs(span), 2.0 * time_ulp(t0));
|
fmax(1e-12 * fabs(span), 2.0 * time_ulp(t0));
|
||||||
*integration_reason = RAY_REASON_PROTOCOL_ERROR;
|
*integration_reason = RAY_REASON_WORLDTUBE_EVALUATION_FAILED;
|
||||||
for (int iteration = 0; iteration < 256; ++iteration) {
|
for (int iteration = 0; iteration < 256; ++iteration) {
|
||||||
if (fabs(span) * (f_hi - f_lo) <= stop_tol)
|
if (fabs(span) * (f_hi - f_lo) <= stop_tol)
|
||||||
break;
|
break;
|
||||||
@@ -1223,25 +1357,34 @@ static int dp_localize_threshold_bracket(const MetricSlab *slab,
|
|||||||
* the step so it cannot cross a motion-segment boundary (which would break the
|
* the step so it cannot cross a motion-segment boundary (which would break the
|
||||||
* constant-velocity polynomial used by the dense event layer). On success
|
* constant-velocity polynomial used by the dense event layer). On success
|
||||||
* *event_left is the tightened left bound; on TIME_RANGE_EXHAUSTED
|
* *event_left is the tightened left bound; on TIME_RANGE_EXHAUSTED
|
||||||
* *time_range_end carries the end that exhausted its history. */
|
* *time_range_end carries the end that exhausted its history. On
|
||||||
|
* ASYMPTOTIC_INVALID `*failure` carries the classified cause
|
||||||
|
* (END_DESCRIPTOR_FAILED / WORLDTUBE_SAMPLE_FAILED / WORLDTUBE_GEOMETRY_INVALID). */
|
||||||
static AsymptoticStatus worldtube_prestep_scan(
|
static AsymptoticStatus worldtube_prestep_scan(
|
||||||
const SpacetimeSource *source, size_t end_count, double t, double step_left,
|
const SpacetimeSource *source, size_t end_count, double t, double step_left,
|
||||||
double *event_left, SpacetimeEndId *time_range_end) {
|
double *event_left, SpacetimeEndId *time_range_end, RayReason *failure) {
|
||||||
*event_left = step_left;
|
*event_left = step_left;
|
||||||
|
*failure = RAY_REASON_ASYMPTOTIC_LIFECYCLE_INVALID;
|
||||||
for (size_t i = 0; i < end_count; ++i) {
|
for (size_t i = 0; i < end_count; ++i) {
|
||||||
SpacetimeAsymptoticEnd end;
|
SpacetimeAsymptoticEnd end;
|
||||||
if (spacetime_asymptotic_end(source, i, &end))
|
if (spacetime_asymptotic_end(source, i, &end)) {
|
||||||
|
*failure = RAY_REASON_END_DESCRIPTOR_FAILED;
|
||||||
return ASYMPTOTIC_INVALID;
|
return ASYMPTOTIC_INVALID;
|
||||||
|
}
|
||||||
SpacetimeEscapeWorldtubeSample sample;
|
SpacetimeEscapeWorldtubeSample sample;
|
||||||
if (spacetime_escape_worldtube_sample(source, end.end_id, t, &sample))
|
if (spacetime_escape_worldtube_sample(source, end.end_id, t, &sample)) {
|
||||||
|
*failure = RAY_REASON_WORLDTUBE_SAMPLE_FAILED;
|
||||||
return ASYMPTOTIC_INVALID;
|
return ASYMPTOTIC_INVALID;
|
||||||
|
}
|
||||||
if (!sample.valid) {
|
if (!sample.valid) {
|
||||||
*time_range_end = end.end_id;
|
*time_range_end = end.end_id;
|
||||||
return ASYMPTOTIC_TIME_RANGE_EXHAUSTED;
|
return ASYMPTOTIC_TIME_RANGE_EXHAUSTED;
|
||||||
}
|
}
|
||||||
if (!(sample.radius > 0.0) || !isfinite(sample.radius) ||
|
if (!(sample.radius > 0.0) || !isfinite(sample.radius) ||
|
||||||
!isfinite(sample.radius_rate))
|
!isfinite(sample.radius_rate)) {
|
||||||
|
*failure = RAY_REASON_WORLDTUBE_GEOMETRY_INVALID;
|
||||||
return ASYMPTOTIC_INVALID;
|
return ASYMPTOTIC_INVALID;
|
||||||
|
}
|
||||||
if (!sample.velocity_constant)
|
if (!sample.velocity_constant)
|
||||||
return ASYMPTOTIC_UNSUPPORTED;
|
return ASYMPTOTIC_UNSUPPORTED;
|
||||||
const double boundary =
|
const double boundary =
|
||||||
@@ -1284,7 +1427,7 @@ static RayReason reason_from_point_status(SpacetimePointStatus status) {
|
|||||||
case SPACETIME_POINT_INVALID_METRIC:
|
case SPACETIME_POINT_INVALID_METRIC:
|
||||||
return RAY_REASON_INVALID_METRIC;
|
return RAY_REASON_INVALID_METRIC;
|
||||||
case SPACETIME_POINT_INTERNAL_ERROR:
|
case SPACETIME_POINT_INTERNAL_ERROR:
|
||||||
return RAY_REASON_PROTOCOL_ERROR;
|
return RAY_REASON_METRIC_INTERNAL_ERROR;
|
||||||
default:
|
default:
|
||||||
return RAY_REASON_INTEGRATION_ERROR;
|
return RAY_REASON_INTEGRATION_ERROR;
|
||||||
}
|
}
|
||||||
@@ -1370,11 +1513,12 @@ static int threshold_reached(const MetricSlab *slab,
|
|||||||
|
|
||||||
/* Legacy no-declared-ends backends may still report a region escape. There
|
/* Legacy no-declared-ends backends may still report a region escape. There
|
||||||
* is no position-based physical capture; a failed sky direction is reported
|
* is no position-based physical capture; a failed sky direction is reported
|
||||||
* as an integration failure, never as capture. */
|
* as an invalid escape direction (historically an integration failure), never
|
||||||
|
* as capture. */
|
||||||
static GeodesicAdvanceResult legacy_escape(const MetricSlab *slab,
|
static GeodesicAdvanceResult legacy_escape(const MetricSlab *slab,
|
||||||
const State *s, RayEndpoint *out) {
|
const State *s, RayEndpoint *out) {
|
||||||
if (escaped_direction(slab, s->coordinate_time, s, out->n_infinity) != 0) {
|
if (escaped_direction(slab, s->coordinate_time, s, out->n_infinity) != 0) {
|
||||||
set_incomplete(out, RAY_REASON_INTEGRATION_ERROR, s);
|
set_incomplete(out, RAY_REASON_INVALID_ESCAPE_DIRECTION, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
out->outcome = RAY_OUTCOME_ESCAPED;
|
out->outcome = RAY_OUTCOME_ESCAPED;
|
||||||
@@ -1392,7 +1536,7 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
slab_left_time > s->coordinate_time) {
|
slab_left_time > s->coordinate_time) {
|
||||||
if (out != NULL) {
|
if (out != NULL) {
|
||||||
out->outcome = RAY_OUTCOME_INCOMPLETE;
|
out->outcome = RAY_OUTCOME_INCOMPLETE;
|
||||||
out->reason = RAY_REASON_PROTOCOL_ERROR;
|
out->reason = RAY_REASON_INVALID_ARGUMENT;
|
||||||
record_final_state(out, s);
|
record_final_state(out, s);
|
||||||
out->threshold_value = NAN;
|
out->threshold_value = NAN;
|
||||||
}
|
}
|
||||||
@@ -1401,7 +1545,7 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
/* An unknown stepper code must be rejected explicitly, exactly like an
|
/* An unknown stepper code must be rejected explicitly, exactly like an
|
||||||
* unknown wire code; "not DP54" must never fall through to the RK4 path. */
|
* unknown wire code; "not DP54" must never fall through to the RK4 path. */
|
||||||
if (!stepper_known(config->stepper)) {
|
if (!stepper_known(config->stepper)) {
|
||||||
set_incomplete(out, RAY_REASON_PROTOCOL_ERROR, s);
|
set_incomplete(out, RAY_REASON_UNKNOWN_STEPPER, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
const int use_dp = config->stepper == GEODESIC_STEPPER_DP54;
|
const int use_dp = config->stepper == GEODESIC_STEPPER_DP54;
|
||||||
@@ -1412,16 +1556,18 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
out->accepted_step_limit = config->max_steps;
|
out->accepted_step_limit = config->max_steps;
|
||||||
if (use_dp) {
|
if (use_dp) {
|
||||||
if (!dp_config_valid(config)) {
|
if (!dp_config_valid(config)) {
|
||||||
set_incomplete(out, RAY_REASON_PROTOCOL_ERROR, s);
|
set_incomplete(out, RAY_REASON_INVALID_STEPPER_CONFIG, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
} else if (config->coordinate_time_step <= 0 || !config->max_steps) {
|
} else if (config->coordinate_time_step <= 0 || !config->max_steps) {
|
||||||
set_incomplete(out, RAY_REASON_PROTOCOL_ERROR, s);
|
set_incomplete(out, RAY_REASON_INVALID_STEPPER_CONFIG, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
const AsymLifecycleMode mode = asym_lifecycle_mode(slab->source);
|
RayReason lifecycle_reason = RAY_REASON_ASYMPTOTIC_LIFECYCLE_INVALID;
|
||||||
|
const AsymLifecycleMode mode =
|
||||||
|
asym_lifecycle_mode(slab->source, &lifecycle_reason);
|
||||||
if (mode == ASYM_LIFECYCLE_PROTOCOL_ERROR) {
|
if (mode == ASYM_LIFECYCLE_PROTOCOL_ERROR) {
|
||||||
set_incomplete(out, RAY_REASON_PROTOCOL_ERROR, s);
|
set_incomplete(out, lifecycle_reason, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
const int directed = mode == ASYM_LIFECYCLE_READY;
|
const int directed = mode == ASYM_LIFECYCLE_READY;
|
||||||
@@ -1467,9 +1613,10 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
double event_left = step_left;
|
double event_left = step_left;
|
||||||
if (directed) {
|
if (directed) {
|
||||||
SpacetimeEndId tr_end = SPACETIME_END_NONE;
|
SpacetimeEndId tr_end = SPACETIME_END_NONE;
|
||||||
|
RayReason scan_failure = RAY_REASON_ASYMPTOTIC_LIFECYCLE_INVALID;
|
||||||
const AsymptoticStatus scan = worldtube_prestep_scan(
|
const AsymptoticStatus scan = worldtube_prestep_scan(
|
||||||
slab->source, end_count, s->coordinate_time, step_left, &event_left,
|
slab->source, end_count, s->coordinate_time, step_left, &event_left,
|
||||||
&tr_end);
|
&tr_end, &scan_failure);
|
||||||
if (scan == ASYMPTOTIC_UNSUPPORTED) {
|
if (scan == ASYMPTOTIC_UNSUPPORTED) {
|
||||||
set_incomplete(out, RAY_REASON_UNSUPPORTED, s);
|
set_incomplete(out, RAY_REASON_UNSUPPORTED, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
@@ -1480,7 +1627,7 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
return GEODESIC_ADVANCE_TERMINATED;
|
return GEODESIC_ADVANCE_TERMINATED;
|
||||||
}
|
}
|
||||||
if (scan != ASYMPTOTIC_OK) {
|
if (scan != ASYMPTOTIC_OK) {
|
||||||
set_incomplete(out, RAY_REASON_PROTOCOL_ERROR, s);
|
set_incomplete(out, scan_failure, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1506,7 +1653,7 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
SpacetimeAsymptoticEnd end;
|
SpacetimeAsymptoticEnd end;
|
||||||
if (spacetime_asymptotic_end(slab->source, i, &end)) {
|
if (spacetime_asymptotic_end(slab->source, i, &end)) {
|
||||||
restore_accepted_trajectory(s, &before);
|
restore_accepted_trajectory(s, &before);
|
||||||
set_incomplete(out, RAY_REASON_PROTOCOL_ERROR, s);
|
set_incomplete(out, RAY_REASON_END_DESCRIPTOR_FAILED, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
double f_before, f_after;
|
double f_before, f_after;
|
||||||
@@ -1524,14 +1671,16 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
}
|
}
|
||||||
if (before_status != ASYMPTOTIC_OK || after_status != ASYMPTOTIC_OK) {
|
if (before_status != ASYMPTOTIC_OK || after_status != ASYMPTOTIC_OK) {
|
||||||
restore_accepted_trajectory(s, &before);
|
restore_accepted_trajectory(s, &before);
|
||||||
set_incomplete(out, RAY_REASON_PROTOCOL_ERROR, s);
|
set_incomplete(out, RAY_REASON_WORLDTUBE_EVALUATION_FAILED, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
if (f_before > 0.0 || f_after <= 0.0)
|
if (f_before > 0.0 || f_after <= 0.0)
|
||||||
continue;
|
continue;
|
||||||
State crossing;
|
State crossing;
|
||||||
|
RayReason locate_failure = RAY_REASON_ESCAPE_LOCALIZATION_FAILED;
|
||||||
const AsymptoticStatus localized = localize_worldtube_crossing(
|
const AsymptoticStatus localized = localize_worldtube_crossing(
|
||||||
slab, end.end_id, &before, h, &crossing, &rhs_total);
|
slab, end.end_id, &before, h, &crossing, &locate_failure,
|
||||||
|
&rhs_total);
|
||||||
s->rhs_evaluations = rhs_total;
|
s->rhs_evaluations = rhs_total;
|
||||||
s->rejected_steps = reject_total;
|
s->rejected_steps = reject_total;
|
||||||
if (localized == ASYMPTOTIC_TIME_RANGE_EXHAUSTED) {
|
if (localized == ASYMPTOTIC_TIME_RANGE_EXHAUSTED) {
|
||||||
@@ -1542,7 +1691,7 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
}
|
}
|
||||||
if (localized != ASYMPTOTIC_OK) {
|
if (localized != ASYMPTOTIC_OK) {
|
||||||
restore_accepted_trajectory(s, &before);
|
restore_accepted_trajectory(s, &before);
|
||||||
set_incomplete(out, RAY_REASON_PROTOCOL_ERROR, s);
|
set_incomplete(out, locate_failure, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
const AsymptoticStatus transfer = asymptotic_finish_escape(
|
const AsymptoticStatus transfer = asymptotic_finish_escape(
|
||||||
@@ -1567,7 +1716,10 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
return GEODESIC_ADVANCE_TERMINATED;
|
return GEODESIC_ADVANCE_TERMINATED;
|
||||||
}
|
}
|
||||||
restore_accepted_trajectory(s, &before);
|
restore_accepted_trajectory(s, &before);
|
||||||
set_incomplete(out, RAY_REASON_PROTOCOL_ERROR, s);
|
set_incomplete(out, transfer == ASYMPTOTIC_UNSUPPORTED
|
||||||
|
? RAY_REASON_UNSUPPORTED
|
||||||
|
: RAY_REASON_ESCAPE_TRANSFER_FAILED,
|
||||||
|
s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -1615,7 +1767,7 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
SpacetimeAsymptoticEnd end;
|
SpacetimeAsymptoticEnd end;
|
||||||
if (spacetime_asymptotic_end(slab->source, i, &end)) {
|
if (spacetime_asymptotic_end(slab->source, i, &end)) {
|
||||||
restore_accepted_trajectory(s, &step_before);
|
restore_accepted_trajectory(s, &step_before);
|
||||||
set_incomplete(out, RAY_REASON_PROTOCOL_ERROR, s);
|
set_incomplete(out, RAY_REASON_END_DESCRIPTOR_FAILED, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
double f_before, f_after;
|
double f_before, f_after;
|
||||||
@@ -1658,7 +1810,7 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
* a multi-end ray legitimately sits outside some ends. */
|
* a multi-end ray legitimately sits outside some ends. */
|
||||||
if (end_count == 1) {
|
if (end_count == 1) {
|
||||||
restore_accepted_trajectory(s, &step_before);
|
restore_accepted_trajectory(s, &step_before);
|
||||||
set_incomplete(out, RAY_REASON_PROTOCOL_ERROR, s);
|
set_incomplete(out, RAY_REASON_OUTSIDE_WORLDTUBE, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -1732,14 +1884,14 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
if (event_retry_limit(s, config, &step_before, &event_consecutive,
|
if (event_retry_limit(s, config, &step_before, &event_consecutive,
|
||||||
&reject_total)) {
|
&reject_total)) {
|
||||||
s->rhs_evaluations = rhs_total;
|
s->rhs_evaluations = rhs_total;
|
||||||
set_incomplete(out, RAY_REASON_INTEGRATION_ERROR, s);
|
set_incomplete(out, RAY_REASON_ESCAPE_EVENT_UNCONFIRMED, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
retry_step = 1;
|
retry_step = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
State crossing;
|
State crossing;
|
||||||
RayReason event_reason = RAY_REASON_PROTOCOL_ERROR;
|
RayReason event_reason = RAY_REASON_WORLDTUBE_EVALUATION_FAILED;
|
||||||
const AsymptoticStatus localized = dp_localize_worldtube_crossing(
|
const AsymptoticStatus localized = dp_localize_worldtube_crossing(
|
||||||
slab, config, end.end_id, &step_before, h, b_lo, b_hi, &crossing,
|
slab, config, end.end_id, &step_before, h, b_lo, b_hi, &crossing,
|
||||||
&event_reason, &rhs_total, &reject_total);
|
&event_reason, &rhs_total, &reject_total);
|
||||||
@@ -1776,7 +1928,7 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
}
|
}
|
||||||
if (end_fail != ASYMPTOTIC_OK) {
|
if (end_fail != ASYMPTOTIC_OK) {
|
||||||
restore_accepted_trajectory(s, &step_before);
|
restore_accepted_trajectory(s, &step_before);
|
||||||
set_incomplete(out, RAY_REASON_PROTOCOL_ERROR, s);
|
set_incomplete(out, RAY_REASON_WORLDTUBE_EVALUATION_FAILED, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
if (retry_step)
|
if (retry_step)
|
||||||
@@ -1849,7 +2001,7 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
if (event_retry_limit(s, config, &step_before, &event_consecutive,
|
if (event_retry_limit(s, config, &step_before, &event_consecutive,
|
||||||
&reject_total)) {
|
&reject_total)) {
|
||||||
s->rhs_evaluations = rhs_total;
|
s->rhs_evaluations = rhs_total;
|
||||||
set_incomplete(out, RAY_REASON_INTEGRATION_ERROR, s);
|
set_incomplete(out, RAY_REASON_THRESHOLD_EVENT_UNCONFIRMED, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -1870,7 +2022,7 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
if (event_retry_limit(s, config, &step_before, &event_consecutive,
|
if (event_retry_limit(s, config, &step_before, &event_consecutive,
|
||||||
&reject_total)) {
|
&reject_total)) {
|
||||||
s->rhs_evaluations = rhs_total;
|
s->rhs_evaluations = rhs_total;
|
||||||
set_incomplete(out, RAY_REASON_INTEGRATION_ERROR, s);
|
set_incomplete(out, RAY_REASON_THRESHOLD_EVENT_UNCONFIRMED, s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -1932,7 +2084,10 @@ GeodesicAdvanceResult geodesic_advance_past_ray(
|
|||||||
return GEODESIC_ADVANCE_TERMINATED;
|
return GEODESIC_ADVANCE_TERMINATED;
|
||||||
}
|
}
|
||||||
restore_accepted_trajectory(s, &step_before);
|
restore_accepted_trajectory(s, &step_before);
|
||||||
set_incomplete(out, RAY_REASON_PROTOCOL_ERROR, s);
|
set_incomplete(out, transfer == ASYMPTOTIC_UNSUPPORTED
|
||||||
|
? RAY_REASON_UNSUPPORTED
|
||||||
|
: RAY_REASON_ESCAPE_TRANSFER_FAILED,
|
||||||
|
s);
|
||||||
return GEODESIC_ADVANCE_FAILED;
|
return GEODESIC_ADVANCE_FAILED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1971,21 +2126,29 @@ RayEndpoint geodesic_trace_past(const SpacetimeSource *source,
|
|||||||
.accepted_steps = 0,
|
.accepted_steps = 0,
|
||||||
.threshold_value = NAN};
|
.threshold_value = NAN};
|
||||||
record_final_state(&out, NULL);
|
record_final_state(&out, NULL);
|
||||||
if (!source || !observer || !config || fabs(dot(n, n) - 1) > 1e-10)
|
if (!source || !observer || !config || n == NULL) {
|
||||||
|
out.reason = RAY_REASON_INVALID_ARGUMENT;
|
||||||
return out;
|
return out;
|
||||||
|
}
|
||||||
|
const double direction_norm2 = dot(n, n);
|
||||||
|
if (!isfinite(direction_norm2) || fabs(direction_norm2 - 1.0) > 1e-10) {
|
||||||
|
out.reason = RAY_REASON_INVALID_ARGUMENT;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
if (!stepper_known(config->stepper)) {
|
if (!stepper_known(config->stepper)) {
|
||||||
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
||||||
out.reason = RAY_REASON_PROTOCOL_ERROR;
|
out.reason = RAY_REASON_UNKNOWN_STEPPER;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
const int use_dp = config->stepper == GEODESIC_STEPPER_DP54;
|
const int use_dp = config->stepper == GEODESIC_STEPPER_DP54;
|
||||||
if (use_dp) {
|
if (use_dp) {
|
||||||
if (!dp_config_valid(config)) {
|
if (!dp_config_valid(config)) {
|
||||||
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
||||||
out.reason = RAY_REASON_PROTOCOL_ERROR;
|
out.reason = RAY_REASON_INVALID_STEPPER_CONFIG;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
} else if (config->coordinate_time_step <= 0 || !config->max_steps) {
|
} else if (config->coordinate_time_step <= 0 || !config->max_steps) {
|
||||||
|
out.reason = RAY_REASON_INVALID_STEPPER_CONFIG;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
out.lookback_limit = use_dp ? config->max_lookback_time : 0.0;
|
out.lookback_limit = use_dp ? config->max_lookback_time : 0.0;
|
||||||
@@ -2006,7 +2169,7 @@ RayEndpoint geodesic_trace_past(const SpacetimeSource *source,
|
|||||||
}
|
}
|
||||||
if (route_status != ASYMPTOTIC_OK) {
|
if (route_status != ASYMPTOTIC_OK) {
|
||||||
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
||||||
out.reason = RAY_REASON_PROTOCOL_ERROR;
|
out.reason = RAY_REASON_CAMERA_PREROUTE_FAILED;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
if (route.kind == ASYMPTOTIC_ROUTE_ESCAPED) {
|
if (route.kind == ASYMPTOTIC_ROUTE_ESCAPED) {
|
||||||
@@ -2027,7 +2190,7 @@ RayEndpoint geodesic_trace_past(const SpacetimeSource *source,
|
|||||||
if (route.kind != ASYMPTOTIC_ROUTE_INSIDE &&
|
if (route.kind != ASYMPTOTIC_ROUTE_INSIDE &&
|
||||||
route.kind != ASYMPTOTIC_ROUTE_ENTRY) {
|
route.kind != ASYMPTOTIC_ROUTE_ENTRY) {
|
||||||
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
||||||
out.reason = RAY_REASON_PROTOCOL_ERROR;
|
out.reason = RAY_REASON_INVALID_ROUTE_KIND;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
State state = {.coordinate_time = route.activate_t,
|
State state = {.coordinate_time = route.activate_t,
|
||||||
@@ -2055,7 +2218,7 @@ RayEndpoint geodesic_trace_past(const SpacetimeSource *source,
|
|||||||
MetricSlab *slab = NULL;
|
MetricSlab *slab = NULL;
|
||||||
if (spacetime_load_slab(source, route.activate_t, last_time - 1.0, &slab)) {
|
if (spacetime_load_slab(source, route.activate_t, last_time - 1.0, &slab)) {
|
||||||
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
||||||
out.reason = RAY_REASON_IO_ERROR;
|
out.reason = RAY_REASON_SLAB_LOAD_FAILED;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
if (geodesic_advance_past_ray(slab, &state, last_time, config, &out) ==
|
if (geodesic_advance_past_ray(slab, &state, last_time, config, &out) ==
|
||||||
@@ -2081,21 +2244,24 @@ RayEndpoint geodesic_trace_past_from_state(const SpacetimeSource *source,
|
|||||||
.threshold_value = NAN};
|
.threshold_value = NAN};
|
||||||
record_final_state(&out, NULL);
|
record_final_state(&out, NULL);
|
||||||
if (!source || !state_in || !config || !config->max_steps ||
|
if (!source || !state_in || !config || !config->max_steps ||
|
||||||
state_in->steps >= config->max_steps)
|
state_in->steps >= config->max_steps) {
|
||||||
|
out.reason = RAY_REASON_INVALID_ARGUMENT;
|
||||||
return out;
|
return out;
|
||||||
|
}
|
||||||
if (!stepper_known(config->stepper)) {
|
if (!stepper_known(config->stepper)) {
|
||||||
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
||||||
out.reason = RAY_REASON_PROTOCOL_ERROR;
|
out.reason = RAY_REASON_UNKNOWN_STEPPER;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
const int use_dp = config->stepper == GEODESIC_STEPPER_DP54;
|
const int use_dp = config->stepper == GEODESIC_STEPPER_DP54;
|
||||||
if (use_dp) {
|
if (use_dp) {
|
||||||
if (!dp_config_valid(config)) {
|
if (!dp_config_valid(config)) {
|
||||||
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
||||||
out.reason = RAY_REASON_PROTOCOL_ERROR;
|
out.reason = RAY_REASON_INVALID_STEPPER_CONFIG;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
} else if (config->coordinate_time_step <= 0) {
|
} else if (config->coordinate_time_step <= 0) {
|
||||||
|
out.reason = RAY_REASON_INVALID_STEPPER_CONFIG;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
out.lookback_limit = use_dp ? config->max_lookback_time : 0.0;
|
out.lookback_limit = use_dp ? config->max_lookback_time : 0.0;
|
||||||
@@ -2115,7 +2281,7 @@ RayEndpoint geodesic_trace_past_from_state(const SpacetimeSource *source,
|
|||||||
MetricSlab *slab = NULL;
|
MetricSlab *slab = NULL;
|
||||||
if (spacetime_load_slab(source, state.coordinate_time, last_time, &slab)) {
|
if (spacetime_load_slab(source, state.coordinate_time, last_time, &slab)) {
|
||||||
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
out.outcome = RAY_OUTCOME_INCOMPLETE;
|
||||||
out.reason = RAY_REASON_IO_ERROR;
|
out.reason = RAY_REASON_SLAB_LOAD_FAILED;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
if (geodesic_advance_past_ray(slab, &state, last_time, config, &out) ==
|
if (geodesic_advance_past_ray(slab, &state, last_time, config, &out) ==
|
||||||
|
|||||||
+56
-2
@@ -14,7 +14,15 @@ typedef enum {
|
|||||||
} RayOutcome;
|
} RayOutcome;
|
||||||
|
|
||||||
/* Diagnostic reason. Different DARK reasons must not create a mesh seam; the
|
/* Diagnostic reason. Different DARK reasons must not create a mesh seam; the
|
||||||
* reason is for accounting and provenance only. */
|
* reason is for accounting and provenance only.
|
||||||
|
*
|
||||||
|
* Wire compatibility: the original coarse reasons keep their frozen numeric
|
||||||
|
* codes 0..9 exactly. Every appended detail reason therefore has a larger
|
||||||
|
* numeric value, and an older reader that validates `reason > RAY_REASON_IO_ERROR`
|
||||||
|
* safely rejects a map produced with a detail reason instead of silently
|
||||||
|
* reinterpreting it. A current reader accepts the frozen 0..9 codes and the
|
||||||
|
* appended detail codes, and rejects anything at or above RAY_REASON_COUNT.
|
||||||
|
* RAY_REASON_COUNT is a sentinel/count, never a serialized wire value. */
|
||||||
typedef enum {
|
typedef enum {
|
||||||
RAY_REASON_NONE = 0,
|
RAY_REASON_NONE = 0,
|
||||||
RAY_REASON_REDSHIFT_LIMIT,
|
RAY_REASON_REDSHIFT_LIMIT,
|
||||||
@@ -25,13 +33,59 @@ typedef enum {
|
|||||||
RAY_REASON_INTEGRATION_ERROR,
|
RAY_REASON_INTEGRATION_ERROR,
|
||||||
RAY_REASON_UNSUPPORTED,
|
RAY_REASON_UNSUPPORTED,
|
||||||
RAY_REASON_PROTOCOL_ERROR,
|
RAY_REASON_PROTOCOL_ERROR,
|
||||||
RAY_REASON_IO_ERROR
|
RAY_REASON_IO_ERROR,
|
||||||
|
/* ---- Appended detail reasons (ids > RAY_REASON_IO_ERROR). ---- */
|
||||||
|
/* Protocol-derived. */
|
||||||
|
RAY_REASON_INVALID_ARGUMENT, /* null/nonunit/bad left bound */
|
||||||
|
RAY_REASON_UNKNOWN_STEPPER, /* unrecognized stepper code */
|
||||||
|
RAY_REASON_INVALID_STEPPER_CONFIG, /* stepper config rejected */
|
||||||
|
RAY_REASON_ASYMPTOTIC_LIFECYCLE_INVALID, /* declared ends inconsistent */
|
||||||
|
RAY_REASON_CAMERA_PREROUTE_FAILED, /* preroute returned INVALID */
|
||||||
|
RAY_REASON_INVALID_ROUTE_KIND, /* unknown pre-route kind */
|
||||||
|
RAY_REASON_INVALID_CONTINUATION, /* retry without valid payload */
|
||||||
|
RAY_REASON_METRIC_INTERNAL_ERROR, /* SPACETIME_POINT_INTERNAL_ERROR */
|
||||||
|
RAY_REASON_END_DESCRIPTOR_FAILED, /* end descriptor retrieval failed */
|
||||||
|
RAY_REASON_WORLDTUBE_SAMPLE_FAILED, /* worldtube sample callback failed */
|
||||||
|
RAY_REASON_WORLDTUBE_GEOMETRY_INVALID, /* nonfinite/nonpositive radius/rate */
|
||||||
|
RAY_REASON_WORLDTUBE_EVALUATION_FAILED, /* worldtube value eval invalid */
|
||||||
|
RAY_REASON_OUTSIDE_WORLDTUBE, /* single-end pre-step F > geom_tol */
|
||||||
|
RAY_REASON_ESCAPE_LOCALIZATION_FAILED, /* crossing localizer invalid */
|
||||||
|
RAY_REASON_ESCAPE_TRANSFER_FAILED, /* finish_escape returned invalid */
|
||||||
|
RAY_REASON_INVALID_ESCAPE_DIRECTION, /* legacy sky normalize invalid */
|
||||||
|
/* Integration-derived. */
|
||||||
|
RAY_REASON_INVALID_STEP_INTERVAL, /* remaining/proposal nonpositive */
|
||||||
|
RAY_REASON_TIME_STEP_UNREPRESENTABLE, /* target >= t0 after rounding */
|
||||||
|
RAY_REASON_MIN_STEP_REACHED, /* actual_h < min_step */
|
||||||
|
RAY_REASON_REJECTION_LIMIT, /* error-estimate rejections exhausted */
|
||||||
|
RAY_REASON_NONFINITE_TRIAL, /* nonfinite trial hit reject quota */
|
||||||
|
RAY_REASON_SUBINTEGRATION_TARGET_INVALID,/* subintegrate target in future */
|
||||||
|
RAY_REASON_SUBINTEGRATION_TARGET_MISSED, /* subintegr. discrepancy > snap */
|
||||||
|
RAY_REASON_ESCAPE_EVENT_UNCONFIRMED, /* escape event retry exhausted */
|
||||||
|
RAY_REASON_THRESHOLD_EVENT_UNCONFIRMED, /* threshold event retry exhausted */
|
||||||
|
/* I/O-derived. */
|
||||||
|
RAY_REASON_SLAB_LOAD_FAILED, /* spacetime_load_slab failed */
|
||||||
|
RAY_REASON_COUNT /* sentinel: valid ids are < COUNT */
|
||||||
} RayReason;
|
} RayReason;
|
||||||
|
|
||||||
/* Stable name for a RayReason (never NULL; out-of-range values return
|
/* Stable name for a RayReason (never NULL; out-of-range values return
|
||||||
* "UNKNOWN"). Pure and thread-safe, so it may be called from any reporter. */
|
* "UNKNOWN"). Pure and thread-safe, so it may be called from any reporter. */
|
||||||
const char *ray_reason_name(RayReason reason);
|
const char *ray_reason_name(RayReason reason);
|
||||||
|
|
||||||
|
/* Nonzero when `reason` is one of the valid wire/provenance codes
|
||||||
|
* (0 <= reason < RAY_REASON_COUNT). Pure and thread-safe. */
|
||||||
|
int ray_reason_valid(RayReason reason);
|
||||||
|
|
||||||
|
/* Coarse category of a reason, for callers that only need the historical
|
||||||
|
* bucket. The frozen coarse reasons 0..9 map to themselves; each appended
|
||||||
|
* detail reason maps onto one of the existing coarse categories
|
||||||
|
* (PROTOCOL_ERROR / INTEGRATION_ERROR / UNSUPPORTED / IO_ERROR).
|
||||||
|
* ESCAPE_LOCALIZATION_FAILED historically surfaced as PROTOCOL_ERROR (the RK4
|
||||||
|
* crossing localizer collapsed to ASYMPTOTIC_INVALID at its caller) and
|
||||||
|
* INVALID_ESCAPE_DIRECTION historically surfaced as INTEGRATION_ERROR.
|
||||||
|
* Out-of-range values return the documented sentinel RAY_REASON_COUNT. Pure
|
||||||
|
* and thread-safe. */
|
||||||
|
RayReason ray_reason_category(RayReason reason);
|
||||||
|
|
||||||
/* Monitored quantity used by the dark-redshift termination policy. `LOG_P0`
|
/* Monitored quantity used by the dark-redshift termination policy. `LOG_P0`
|
||||||
* is ln(p^0) = L - ln(alpha); it differs from `LOG_ALPHA_P0` by a local
|
* is ln(p^0) = L - ln(alpha); it differs from `LOG_ALPHA_P0` by a local
|
||||||
* function of position and must not be confused with the true infinity
|
* function of position and must not be confused with the true infinity
|
||||||
|
|||||||
+2
-2
@@ -123,7 +123,7 @@ static int valid_mesh(const FrameLensMesh *m) {
|
|||||||
for (size_t i = 0; i < m->vertex_count; ++i) {
|
for (size_t i = 0; i < m->vertex_count; ++i) {
|
||||||
const LensVertex *v = &m->vertices[i];
|
const LensVertex *v = &m->vertices[i];
|
||||||
if (!v->traced || v->outcome > RAY_OUTCOME_INCOMPLETE ||
|
if (!v->traced || v->outcome > RAY_OUTCOME_INCOMPLETE ||
|
||||||
v->reason > RAY_REASON_IO_ERROR || !isfinite(v->image_x) ||
|
!ray_reason_valid(v->reason) || !isfinite(v->image_x) ||
|
||||||
!isfinite(v->image_y) || !isfinite(v->log_frequency_ratio) ||
|
!isfinite(v->image_y) || !isfinite(v->log_frequency_ratio) ||
|
||||||
!unit_vector(v->camera_direction))
|
!unit_vector(v->camera_direction))
|
||||||
return 0;
|
return 0;
|
||||||
@@ -296,7 +296,7 @@ int lens_map_read(const char *path, LensMapProvenance *provenance,
|
|||||||
failed = failed || read_double(file, &v->log_frequency_ratio, &crc) ||
|
failed = failed || read_double(file, &v->log_frequency_ratio, &crc) ||
|
||||||
read_u32(file, &end_id, &crc) || read_u32(file, &outcome, &crc) ||
|
read_u32(file, &end_id, &crc) || read_u32(file, &outcome, &crc) ||
|
||||||
read_u32(file, &reason, &crc) || outcome > RAY_OUTCOME_INCOMPLETE ||
|
read_u32(file, &reason, &crc) || outcome > RAY_OUTCOME_INCOMPLETE ||
|
||||||
reason > RAY_REASON_IO_ERROR;
|
!ray_reason_valid((RayReason)reason);
|
||||||
v->end_id = (SpacetimeEndId)end_id;
|
v->end_id = (SpacetimeEndId)end_id;
|
||||||
v->outcome = (RayOutcome)outcome;
|
v->outcome = (RayOutcome)outcome;
|
||||||
v->reason = (RayReason)reason;
|
v->reason = (RayReason)reason;
|
||||||
|
|||||||
+10
-9
@@ -974,7 +974,7 @@ static void ray_pool_status_counts(const RayPool *rays, size_t *pending,
|
|||||||
* result visible with its reason and frame.
|
* result visible with its reason and frame.
|
||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
#define RAY_DIAG_REASON_UNKNOWN_INDEX ((size_t)RAY_REASON_IO_ERROR + 1u)
|
#define RAY_DIAG_REASON_UNKNOWN_INDEX ((size_t)RAY_REASON_COUNT)
|
||||||
#define RAY_DIAG_REASON_COUNT (RAY_DIAG_REASON_UNKNOWN_INDEX + 1u)
|
#define RAY_DIAG_REASON_COUNT (RAY_DIAG_REASON_UNKNOWN_INDEX + 1u)
|
||||||
#define RAY_DIAG_REPS_PER_REASON 3u
|
#define RAY_DIAG_REPS_PER_REASON 3u
|
||||||
|
|
||||||
@@ -1076,7 +1076,7 @@ static void raydiag_report_flush(const RayDiagReport *report, size_t frame_id,
|
|||||||
long long generation, long long slab) {
|
long long generation, long long slab) {
|
||||||
if (report->total == 0)
|
if (report->total == 0)
|
||||||
return;
|
return;
|
||||||
fprintf(stderr, "Ray failures: frame %zu camera_t=%.9g phase=%s", frame_id,
|
fprintf(stderr, "Ray failures: frame %zu camera_t=%.17g phase=%s", frame_id,
|
||||||
camera_time, phase);
|
camera_time, phase);
|
||||||
if (generation >= 0)
|
if (generation >= 0)
|
||||||
fprintf(stderr, " generation=%lld", generation);
|
fprintf(stderr, " generation=%lld", generation);
|
||||||
@@ -1108,12 +1108,12 @@ static void raydiag_report_flush(const RayDiagReport *report, size_t frame_id,
|
|||||||
if (d->has_persistent)
|
if (d->has_persistent)
|
||||||
fprintf(stderr, " vertex=%zu", d->persistent_id);
|
fprintf(stderr, " vertex=%zu", d->persistent_id);
|
||||||
if (d->has_film)
|
if (d->has_film)
|
||||||
fprintf(stderr, " film=(%.6g, %.6g)", d->film_x, d->film_y);
|
fprintf(stderr, " film=(%.17g, %.17g)", d->film_x, d->film_y);
|
||||||
fprintf(stderr, " trusted=%d", d->trusted);
|
fprintf(stderr, " trusted=%d", d->trusted);
|
||||||
if (d->trusted) {
|
if (d->trusted) {
|
||||||
fprintf(stderr, " stop_t=%.9g", d->stop_time);
|
fprintf(stderr, " stop_t=%.17g", d->stop_time);
|
||||||
if (d->has_position)
|
if (d->has_position)
|
||||||
fprintf(stderr, " pos=(%.9g, %.9g, %.9g)", d->position[0],
|
fprintf(stderr, " pos=(%.17g, %.17g, %.17g)", d->position[0],
|
||||||
d->position[1], d->position[2]);
|
d->position[1], d->position[2]);
|
||||||
}
|
}
|
||||||
fprintf(stderr, " accepted=%llu rejected=%llu rhs=%llu\n", d->accepted,
|
fprintf(stderr, " accepted=%llu rejected=%llu rhs=%llu\n", d->accepted,
|
||||||
@@ -1349,7 +1349,7 @@ static void report_unresolved_frame(const Settings *s,
|
|||||||
mesh->vertices[i].outcome == RAY_OUTCOME_UNRESOLVED)
|
mesh->vertices[i].outcome == RAY_OUTCOME_UNRESOLVED)
|
||||||
++unresolved_vertices;
|
++unresolved_vertices;
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"UNRESOLVED/BUDGET_EXHAUSTED: frame %zu camera_t=%.9g "
|
"UNRESOLVED/BUDGET_EXHAUSTED: frame %zu camera_t=%.17g "
|
||||||
"blocking_triangles=%zu unresolved_samples=%zu\n",
|
"blocking_triangles=%zu unresolved_samples=%zu\n",
|
||||||
frame_id, camera_time, stats->budget_incomplete_triangles,
|
frame_id, camera_time, stats->budget_incomplete_triangles,
|
||||||
unresolved_vertices);
|
unresolved_vertices);
|
||||||
@@ -1362,11 +1362,11 @@ static void report_unresolved_frame(const Settings *s,
|
|||||||
if (!v->traced || v->outcome != RAY_OUTCOME_UNRESOLVED)
|
if (!v->traced || v->outcome != RAY_OUTCOME_UNRESOLVED)
|
||||||
continue;
|
continue;
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
" unresolved sample: frame=%zu vertex=%zu film=(%.6g, %.6g) "
|
" unresolved sample: frame=%zu vertex=%zu film=(%.17g, %.17g) "
|
||||||
"continuation_t=",
|
"continuation_t=",
|
||||||
frame_id, i, v->image_x, v->image_y);
|
frame_id, i, v->image_x, v->image_y);
|
||||||
if (continuation_valid)
|
if (continuation_valid)
|
||||||
fprintf(stderr, "%.9g", v->continuation_t);
|
fprintf(stderr, "%.17g", v->continuation_t);
|
||||||
else
|
else
|
||||||
fputs("-", stderr);
|
fputs("-", stderr);
|
||||||
fprintf(stderr, " accepted=%llu rejected=%llu rhs=%llu\n",
|
fprintf(stderr, " accepted=%llu rejected=%llu rhs=%llu\n",
|
||||||
@@ -2201,7 +2201,8 @@ static int trace_movie_generation(Movie *movie, const Settings *s,
|
|||||||
if (frame_vertex_continuation_state(&fs->vertex, &state)) {
|
if (frame_vertex_continuation_state(&fs->vertex, &state)) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Ray trace generation %zu: frame %zu sample %zu retry is "
|
"Ray trace generation %zu: frame %zu sample %zu retry is "
|
||||||
"missing a valid continuation payload.\n",
|
"missing a valid continuation payload "
|
||||||
|
"(reason=INVALID_CONTINUATION).\n",
|
||||||
generation, movie->frames[f].frame_id, sample);
|
generation, movie->frames[f].frame_id, sample);
|
||||||
free(reported);
|
free(reported);
|
||||||
ray_pool_destroy(&rays);
|
ray_pool_destroy(&rays);
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ void ray_pool_preroute(RayPool *p, const SpacetimeSource *source) {
|
|||||||
p->endpoint[i].outcome = RAY_OUTCOME_INCOMPLETE;
|
p->endpoint[i].outcome = RAY_OUTCOME_INCOMPLETE;
|
||||||
p->endpoint[i].reason = status == ASYMPTOTIC_UNSUPPORTED
|
p->endpoint[i].reason = status == ASYMPTOTIC_UNSUPPORTED
|
||||||
? RAY_REASON_UNSUPPORTED
|
? RAY_REASON_UNSUPPORTED
|
||||||
: RAY_REASON_PROTOCOL_ERROR;
|
: RAY_REASON_CAMERA_PREROUTE_FAILED;
|
||||||
p->endpoint[i].end_id = route.end_id;
|
p->endpoint[i].end_id = route.end_id;
|
||||||
p->status[i] = RAY_POOL_FAILED;
|
p->status[i] = RAY_POOL_FAILED;
|
||||||
continue;
|
continue;
|
||||||
@@ -182,7 +182,7 @@ void ray_pool_preroute(RayPool *p, const SpacetimeSource *source) {
|
|||||||
if (route.kind != ASYMPTOTIC_ROUTE_INSIDE &&
|
if (route.kind != ASYMPTOTIC_ROUTE_INSIDE &&
|
||||||
route.kind != ASYMPTOTIC_ROUTE_ENTRY) {
|
route.kind != ASYMPTOTIC_ROUTE_ENTRY) {
|
||||||
p->endpoint[i].outcome = RAY_OUTCOME_INCOMPLETE;
|
p->endpoint[i].outcome = RAY_OUTCOME_INCOMPLETE;
|
||||||
p->endpoint[i].reason = RAY_REASON_PROTOCOL_ERROR;
|
p->endpoint[i].reason = RAY_REASON_INVALID_ROUTE_KIND;
|
||||||
p->status[i] = RAY_POOL_FAILED;
|
p->status[i] = RAY_POOL_FAILED;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-1
@@ -499,11 +499,61 @@ static void test_end_protocol_error(void) {
|
|||||||
CHECK(geodesic_advance_past_ray(slab, &state, -10.0, &config, &endpoint) ==
|
CHECK(geodesic_advance_past_ray(slab, &state, -10.0, &config, &endpoint) ==
|
||||||
GEODESIC_ADVANCE_FAILED &&
|
GEODESIC_ADVANCE_FAILED &&
|
||||||
endpoint.outcome == RAY_OUTCOME_INCOMPLETE &&
|
endpoint.outcome == RAY_OUTCOME_INCOMPLETE &&
|
||||||
endpoint.reason == RAY_REASON_PROTOCOL_ERROR,
|
endpoint.reason == RAY_REASON_END_DESCRIPTOR_FAILED,
|
||||||
"advance rejects a declared-but-broken end without legacy");
|
"advance rejects a declared-but-broken end without legacy");
|
||||||
spacetime_free_slab(slab);
|
spacetime_free_slab(slab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A worldtube sample callback failure and an invalid worldtube geometry are
|
||||||
|
* distinguished from each other and from the lifecycle/descriptor failure
|
||||||
|
* above when the same advance entry point classifies its pre-step scan. */
|
||||||
|
static void test_advance_worldtube_failure_reasons(void) {
|
||||||
|
const GeodesicTraceConfig config = {.coordinate_time_step = 1.0,
|
||||||
|
.max_steps = 10};
|
||||||
|
GeodesicRayState state = {.coordinate_time = 0.0,
|
||||||
|
.x = {1.0, 0.0, 0.0},
|
||||||
|
.Pi = {0.0, 0.0, 0.0},
|
||||||
|
.log_alpha_p0 = 0.0,
|
||||||
|
.steps = 0};
|
||||||
|
|
||||||
|
SyntheticContext sample_fail = {.radius = 20.0,
|
||||||
|
.valid_t_min = -1.0e30,
|
||||||
|
.constant = 1,
|
||||||
|
.sample_callback_fails = 1};
|
||||||
|
SpacetimeSource source = {.ops = &synthetic_ops, .context = &sample_fail};
|
||||||
|
MetricSlab *slab = NULL;
|
||||||
|
CHECK(spacetime_load_slab(&source, 0.0, -10.0, &slab) == 0,
|
||||||
|
"sample-fail slab");
|
||||||
|
RayEndpoint endpoint = {.frequency_ratio = 0.0,
|
||||||
|
.magnification = 1.0,
|
||||||
|
.end_id = SPACETIME_END_NONE,
|
||||||
|
.outcome = RAY_OUTCOME_INCOMPLETE};
|
||||||
|
CHECK(geodesic_advance_past_ray(slab, &state, -10.0, &config, &endpoint) ==
|
||||||
|
GEODESIC_ADVANCE_FAILED &&
|
||||||
|
endpoint.outcome == RAY_OUTCOME_INCOMPLETE &&
|
||||||
|
endpoint.reason == RAY_REASON_WORLDTUBE_SAMPLE_FAILED,
|
||||||
|
"worldtube sample callback failure is its own reason");
|
||||||
|
spacetime_free_slab(slab);
|
||||||
|
|
||||||
|
SyntheticContext bad_geometry = {.radius = 20.0,
|
||||||
|
.valid_t_min = -1.0e30,
|
||||||
|
.constant = 1,
|
||||||
|
.sample_nan_radius = 1};
|
||||||
|
source = (SpacetimeSource){.ops = &synthetic_ops, .context = &bad_geometry};
|
||||||
|
CHECK(spacetime_load_slab(&source, 0.0, -10.0, &slab) == 0,
|
||||||
|
"geometry slab");
|
||||||
|
endpoint = (RayEndpoint){.frequency_ratio = 0.0,
|
||||||
|
.magnification = 1.0,
|
||||||
|
.end_id = SPACETIME_END_NONE,
|
||||||
|
.outcome = RAY_OUTCOME_INCOMPLETE};
|
||||||
|
CHECK(geodesic_advance_past_ray(slab, &state, -10.0, &config, &endpoint) ==
|
||||||
|
GEODESIC_ADVANCE_FAILED &&
|
||||||
|
endpoint.outcome == RAY_OUTCOME_INCOMPLETE &&
|
||||||
|
endpoint.reason == RAY_REASON_WORLDTUBE_GEOMETRY_INVALID,
|
||||||
|
"invalid worldtube geometry is its own reason");
|
||||||
|
spacetime_free_slab(slab);
|
||||||
|
}
|
||||||
|
|
||||||
static void test_interior_crossing_bisection_failure(void) {
|
static void test_interior_crossing_bisection_failure(void) {
|
||||||
/* radius 20.3 makes the exit land strictly between steps: the accepted
|
/* radius 20.3 makes the exit land strictly between steps: the accepted
|
||||||
* step goes from F < 0 (t = -119.7) to F > 0 (t = -120.7). The invalid
|
* step goes from F < 0 (t = -119.7) to F > 0 (t = -120.7). The invalid
|
||||||
@@ -753,6 +803,7 @@ int main(void) {
|
|||||||
test_motion_segment_domain();
|
test_motion_segment_domain();
|
||||||
test_schwarzschild_sample_failures();
|
test_schwarzschild_sample_failures();
|
||||||
test_end_protocol_error();
|
test_end_protocol_error();
|
||||||
|
test_advance_worldtube_failure_reasons();
|
||||||
test_nonconstant_preroute_unsupported();
|
test_nonconstant_preroute_unsupported();
|
||||||
test_piecewise_constant_history_hole();
|
test_piecewise_constant_history_hole();
|
||||||
test_interior_history_exhaustion();
|
test_interior_history_exhaustion();
|
||||||
|
|||||||
@@ -1418,6 +1418,10 @@ int main(void) {
|
|||||||
.outcome = RAY_OUTCOME_DARK,
|
.outcome = RAY_OUTCOME_DARK,
|
||||||
.reason = RAY_REASON_REDSHIFT_LIMIT,
|
.reason = RAY_REASON_REDSHIFT_LIMIT,
|
||||||
.end_id = SPACETIME_END_NONE, .traced = 1};
|
.end_id = SPACETIME_END_NONE, .traced = 1};
|
||||||
|
/* An appended detail reason with its coherent outcome must survive the
|
||||||
|
* frozen v3 schema exactly. */
|
||||||
|
dv[2].outcome = RAY_OUTCOME_INCOMPLETE;
|
||||||
|
dv[2].reason = RAY_REASON_REJECTION_LIMIT;
|
||||||
dv[0].trace_accepted_steps = 11; dv[0].trace_rejected_steps = 2;
|
dv[0].trace_accepted_steps = 11; dv[0].trace_rejected_steps = 2;
|
||||||
dv[0].trace_rhs_evaluations = 79;
|
dv[0].trace_rhs_evaluations = 79;
|
||||||
dv[1].trace_accepted_steps = 5;
|
dv[1].trace_accepted_steps = 5;
|
||||||
@@ -1458,6 +1462,8 @@ int main(void) {
|
|||||||
dloaded.frames[0].mesh.vertices[0].trace_rejected_steps != 2 ||
|
dloaded.frames[0].mesh.vertices[0].trace_rejected_steps != 2 ||
|
||||||
dloaded.frames[0].mesh.vertices[0].trace_rhs_evaluations != 79 ||
|
dloaded.frames[0].mesh.vertices[0].trace_rhs_evaluations != 79 ||
|
||||||
dloaded.frames[0].mesh.vertices[1].trace_accepted_steps != 5 ||
|
dloaded.frames[0].mesh.vertices[1].trace_accepted_steps != 5 ||
|
||||||
|
dloaded.frames[0].mesh.vertices[2].outcome != RAY_OUTCOME_INCOMPLETE ||
|
||||||
|
dloaded.frames[0].mesh.vertices[2].reason != RAY_REASON_REJECTION_LIMIT ||
|
||||||
dloaded.frames[0].mesh.triangles[0].level != 1) {
|
dloaded.frames[0].mesh.triangles[0].level != 1) {
|
||||||
fputs("lens-map v3 DP54 field round-trip regression failed\n", stderr);
|
fputs("lens-map v3 DP54 field round-trip regression failed\n", stderr);
|
||||||
lens_map_destroy(&dloaded); unlink(dp_path); goto done;
|
lens_map_destroy(&dloaded); unlink(dp_path); goto done;
|
||||||
@@ -1497,6 +1503,41 @@ int main(void) {
|
|||||||
lens_map_destroy(&dloaded);
|
lens_map_destroy(&dloaded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* RAY_REASON_COUNT is a sentinel, never a valid wire reason: a map that
|
||||||
|
* stores it (or anything above it) must be rejected rather than
|
||||||
|
* reinterpreted. Vertex 0's reason field starts at byte 304 in this v3
|
||||||
|
* layout (176 provenance + 48 frame header + 80 vertex prefix). */
|
||||||
|
{
|
||||||
|
if (lens_map_write(dp_path, 4, 3, 30.0, &dp, &df, 1)) {
|
||||||
|
fputs("lens-map sentinel-reason fixture write failed\n", stderr);
|
||||||
|
unlink(dp_path); goto done;
|
||||||
|
}
|
||||||
|
const uint32_t sentinel = (uint32_t)RAY_REASON_COUNT;
|
||||||
|
FILE *bad = fopen(dp_path, "r+b");
|
||||||
|
int bad_failed = bad == NULL || fseek(bad, 304, SEEK_SET) ||
|
||||||
|
fwrite(&sentinel, sizeof sentinel, 1, bad) != 1;
|
||||||
|
if (bad != NULL && fclose(bad)) bad_failed = 1;
|
||||||
|
if (bad_failed || !lens_map_read(dp_path, NULL, &dloaded) ||
|
||||||
|
dloaded.frames != NULL || dloaded.frame_count != 0) {
|
||||||
|
fputs("lens-map sentinel reason rejection regression failed\n", stderr);
|
||||||
|
lens_map_destroy(&dloaded); unlink(dp_path); goto done;
|
||||||
|
}
|
||||||
|
lens_map_destroy(&dloaded);
|
||||||
|
}
|
||||||
|
/* The in-memory writer must also reject the sentinel; the wire test above
|
||||||
|
* cannot isolate reason validation from the frame CRC. */
|
||||||
|
{
|
||||||
|
const RayReason saved_reason = dv[2].reason;
|
||||||
|
dv[2].reason = (RayReason)RAY_REASON_COUNT;
|
||||||
|
const int rejected =
|
||||||
|
lens_map_write(dp_path, 4, 3, 30.0, &dp, &df, 1) != 0;
|
||||||
|
dv[2].reason = saved_reason;
|
||||||
|
if (!rejected) {
|
||||||
|
fputs("lens-map in-memory sentinel reason write regression failed\n",
|
||||||
|
stderr);
|
||||||
|
unlink(dp_path); goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
unlink(dp_path);
|
unlink(dp_path);
|
||||||
}
|
}
|
||||||
/* Legacy v2 import: a real v2 map (no adaptive fields, no cost counters)
|
/* Legacy v2 import: a real v2 map (no adaptive fields, no cost counters)
|
||||||
|
|||||||
+94
-17
@@ -7,26 +7,103 @@
|
|||||||
|
|
||||||
static int nearly_equal(double a, double b) { return fabs(a - b) < 1e-12; }
|
static int nearly_equal(double a, double b) { return fabs(a - b) < 1e-12; }
|
||||||
|
|
||||||
/* Every enumerator must have a stable label, and an out-of-range value must be
|
/* Every enumerator must have a stable label, appended detail codes must be
|
||||||
* reported as UNKNOWN rather than indexing past a table. */
|
* distinct and valid, the coarse category helper must stay inside the coarse
|
||||||
|
* range, and both out-of-range directions (negative and >= COUNT) must be
|
||||||
|
* rejected as UNKNOWN/sentinel. The frozen wire ids 0..9 are pinned. */
|
||||||
static int check_reason_names(void) {
|
static int check_reason_names(void) {
|
||||||
const RayReason reasons[] = {
|
static const char *const frozen[] = {
|
||||||
RAY_REASON_NONE, RAY_REASON_REDSHIFT_LIMIT,
|
"NONE", "REDSHIFT_LIMIT", "BUDGET_EXHAUSTED",
|
||||||
RAY_REASON_BUDGET_EXHAUSTED, RAY_REASON_TIME_RANGE_EXHAUSTED,
|
"TIME_RANGE_EXHAUSTED", "OUT_OF_DOMAIN", "INVALID_METRIC",
|
||||||
RAY_REASON_OUT_OF_DOMAIN, RAY_REASON_INVALID_METRIC,
|
"INTEGRATION_ERROR", "UNSUPPORTED", "PROTOCOL_ERROR",
|
||||||
RAY_REASON_INTEGRATION_ERROR, RAY_REASON_UNSUPPORTED,
|
"IO_ERROR"};
|
||||||
RAY_REASON_PROTOCOL_ERROR, RAY_REASON_IO_ERROR};
|
|
||||||
int failed = 0;
|
int failed = 0;
|
||||||
for (size_t i = 0; i < sizeof reasons / sizeof reasons[0]; ++i) {
|
for (size_t i = 0; i < sizeof frozen / sizeof frozen[0]; ++i) {
|
||||||
const char *name = ray_reason_name(reasons[i]);
|
const RayReason r = (RayReason)i;
|
||||||
if (name == NULL || name[0] == '\0' || strcmp(name, "UNKNOWN") == 0) {
|
if (!ray_reason_valid(r) || strcmp(ray_reason_name(r), frozen[i]) != 0 ||
|
||||||
fprintf(stderr, "reason %d has no stable label\n", (int)reasons[i]);
|
ray_reason_category(r) != r) {
|
||||||
|
fprintf(stderr, "frozen reason id %zu is not stable\n", i);
|
||||||
failed = 1;
|
failed = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const int unknown = (int)RAY_REASON_IO_ERROR + 7;
|
if ((unsigned)RAY_REASON_IO_ERROR + 1u >= (unsigned)RAY_REASON_COUNT) {
|
||||||
if (strcmp(ray_reason_name((RayReason)unknown), "UNKNOWN") != 0) {
|
fputs("no appended detail reasons\n", stderr);
|
||||||
fputs("out-of-range reason is not UNKNOWN\n", stderr);
|
failed = 1;
|
||||||
|
}
|
||||||
|
for (unsigned i = 0; i < (unsigned)RAY_REASON_COUNT; ++i) {
|
||||||
|
const RayReason r = (RayReason)i;
|
||||||
|
const char *name = ray_reason_name(r);
|
||||||
|
if (!ray_reason_valid(r) || name == NULL || name[0] == '\0' ||
|
||||||
|
strcmp(name, "UNKNOWN") == 0) {
|
||||||
|
fprintf(stderr, "reason %u has no stable label\n", i);
|
||||||
|
failed = 1;
|
||||||
|
}
|
||||||
|
if (!ray_reason_valid(ray_reason_category(r))) {
|
||||||
|
fprintf(stderr, "reason %u has no valid category\n", i);
|
||||||
|
failed = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ray_reason_category(RAY_REASON_WORLDTUBE_SAMPLE_FAILED) !=
|
||||||
|
RAY_REASON_PROTOCOL_ERROR ||
|
||||||
|
ray_reason_category(RAY_REASON_OUTSIDE_WORLDTUBE) !=
|
||||||
|
RAY_REASON_PROTOCOL_ERROR ||
|
||||||
|
ray_reason_category(RAY_REASON_ESCAPE_LOCALIZATION_FAILED) !=
|
||||||
|
RAY_REASON_PROTOCOL_ERROR ||
|
||||||
|
ray_reason_category(RAY_REASON_REJECTION_LIMIT) !=
|
||||||
|
RAY_REASON_INTEGRATION_ERROR ||
|
||||||
|
ray_reason_category(RAY_REASON_INVALID_ESCAPE_DIRECTION) !=
|
||||||
|
RAY_REASON_INTEGRATION_ERROR ||
|
||||||
|
ray_reason_category(RAY_REASON_SLAB_LOAD_FAILED) != RAY_REASON_IO_ERROR) {
|
||||||
|
fputs("detail reasons map to the wrong coarse category\n", stderr);
|
||||||
|
failed = 1;
|
||||||
|
}
|
||||||
|
if (ray_reason_valid((RayReason)RAY_REASON_COUNT) ||
|
||||||
|
ray_reason_valid((RayReason)-1)) {
|
||||||
|
fputs("out-of-range reason accepted as valid\n", stderr);
|
||||||
|
failed = 1;
|
||||||
|
}
|
||||||
|
if (strcmp(ray_reason_name((RayReason)RAY_REASON_COUNT), "UNKNOWN") != 0) {
|
||||||
|
fputs("sentinel reason name is not UNKNOWN\n", stderr);
|
||||||
|
failed = 1;
|
||||||
|
}
|
||||||
|
const RayReason unknown = (RayReason)((unsigned)RAY_REASON_COUNT + 7u);
|
||||||
|
if (strcmp(ray_reason_name(unknown), "UNKNOWN") != 0 ||
|
||||||
|
ray_reason_valid(unknown) ||
|
||||||
|
ray_reason_category(unknown) != (RayReason)RAY_REASON_COUNT) {
|
||||||
|
fputs("unknown reason is not the UNKNOWN sentinel\n", stderr);
|
||||||
|
failed = 1;
|
||||||
|
}
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A NULL, non-finite or non-unit direction must be rejected as INVALID_ARGUMENT
|
||||||
|
* without dereferencing the direction or running any trace. */
|
||||||
|
static int check_invalid_directions(const SpacetimeSource *source,
|
||||||
|
const ObserverState *observer) {
|
||||||
|
const GeodesicTraceConfig config = {.coordinate_time_step = 0.25,
|
||||||
|
.max_steps = 100};
|
||||||
|
int failed = 0;
|
||||||
|
const RayEndpoint null_dir =
|
||||||
|
geodesic_trace_past(source, observer, NULL, &config);
|
||||||
|
if (null_dir.outcome != RAY_OUTCOME_INCOMPLETE ||
|
||||||
|
null_dir.reason != RAY_REASON_INVALID_ARGUMENT) {
|
||||||
|
fputs("NULL direction is not INVALID_ARGUMENT\n", stderr);
|
||||||
|
failed = 1;
|
||||||
|
}
|
||||||
|
const double nan_dir[3] = {NAN, 0.0, 0.0};
|
||||||
|
const RayEndpoint nan =
|
||||||
|
geodesic_trace_past(source, observer, nan_dir, &config);
|
||||||
|
if (nan.outcome != RAY_OUTCOME_INCOMPLETE ||
|
||||||
|
nan.reason != RAY_REASON_INVALID_ARGUMENT) {
|
||||||
|
fputs("NaN direction is not INVALID_ARGUMENT\n", stderr);
|
||||||
|
failed = 1;
|
||||||
|
}
|
||||||
|
const double nonunit_dir[3] = {2.0, 0.0, 0.0};
|
||||||
|
const RayEndpoint nonunit =
|
||||||
|
geodesic_trace_past(source, observer, nonunit_dir, &config);
|
||||||
|
if (nonunit.outcome != RAY_OUTCOME_INCOMPLETE ||
|
||||||
|
nonunit.reason != RAY_REASON_INVALID_ARGUMENT) {
|
||||||
|
fputs("non-unit direction is not INVALID_ARGUMENT\n", stderr);
|
||||||
failed = 1;
|
failed = 1;
|
||||||
}
|
}
|
||||||
return failed;
|
return failed;
|
||||||
@@ -35,8 +112,7 @@ static int check_reason_names(void) {
|
|||||||
static int check_ray(const SpacetimeSource *source,
|
static int check_ray(const SpacetimeSource *source,
|
||||||
const ObserverState *observer,
|
const ObserverState *observer,
|
||||||
const double local_direction[3],
|
const double local_direction[3],
|
||||||
const double expected[3]) {
|
const double expected[3]) { const GeodesicTraceConfig config = {.coordinate_time_step = 0.25,
|
||||||
const GeodesicTraceConfig config = {.coordinate_time_step = 0.25,
|
|
||||||
.max_steps = 100};
|
.max_steps = 100};
|
||||||
RayEndpoint ray =
|
RayEndpoint ray =
|
||||||
geodesic_trace_past(source, observer, local_direction, &config);
|
geodesic_trace_past(source, observer, local_direction, &config);
|
||||||
@@ -63,6 +139,7 @@ int main(void) {
|
|||||||
!spacetime_slab_eval(slab, 0.25, (double[]){0.0, 0.0, 0.0}, &metric))
|
!spacetime_slab_eval(slab, 0.25, (double[]){0.0, 0.0, 0.0}, &metric))
|
||||||
return 1;
|
return 1;
|
||||||
int result = check_reason_names() ||
|
int result = check_reason_names() ||
|
||||||
|
check_invalid_directions(&source, &observer) ||
|
||||||
check_ray(&source, &observer, (double[]){1.0, 0.0, 0.0},
|
check_ray(&source, &observer, (double[]){1.0, 0.0, 0.0},
|
||||||
(double[]){0.0, 0.0, -1.0}) ||
|
(double[]){0.0, 0.0, -1.0}) ||
|
||||||
check_ray(&source, &observer, (double[]){0.0, 0.0, 1.0},
|
check_ray(&source, &observer, (double[]){0.0, 0.0, 1.0},
|
||||||
|
|||||||
@@ -345,26 +345,26 @@ static void test_minkowski_finite_interval(void) {
|
|||||||
RayEndpoint out = blank_endpoint();
|
RayEndpoint out = blank_endpoint();
|
||||||
CHECK(geodesic_advance_past_ray(s, &bad_state, -0.5, &bad, &out) ==
|
CHECK(geodesic_advance_past_ray(s, &bad_state, -0.5, &bad, &out) ==
|
||||||
GEODESIC_ADVANCE_FAILED &&
|
GEODESIC_ADVANCE_FAILED &&
|
||||||
out.reason == RAY_REASON_PROTOCOL_ERROR,
|
out.reason == RAY_REASON_INVALID_STEPPER_CONFIG,
|
||||||
"zero atol rejected");
|
"zero atol rejected");
|
||||||
bad = dp_config(1e-9, 0.5, 1.0e6);
|
bad = dp_config(1e-9, 0.5, 1.0e6);
|
||||||
bad.max_lookback_time = 0.0;
|
bad.max_lookback_time = 0.0;
|
||||||
CHECK(geodesic_advance_past_ray(s, &bad_state, -0.5, &bad, &out) ==
|
CHECK(geodesic_advance_past_ray(s, &bad_state, -0.5, &bad, &out) ==
|
||||||
GEODESIC_ADVANCE_FAILED &&
|
GEODESIC_ADVANCE_FAILED &&
|
||||||
out.reason == RAY_REASON_PROTOCOL_ERROR,
|
out.reason == RAY_REASON_INVALID_STEPPER_CONFIG,
|
||||||
"zero lookback rejected");
|
"zero lookback rejected");
|
||||||
bad = dp_config(1e-9, 0.5, 1.0e6);
|
bad = dp_config(1e-9, 0.5, 1.0e6);
|
||||||
bad.min_step = 2.0;
|
bad.min_step = 2.0;
|
||||||
bad.max_step = 1.0;
|
bad.max_step = 1.0;
|
||||||
CHECK(geodesic_advance_past_ray(s, &bad_state, -0.5, &bad, &out) ==
|
CHECK(geodesic_advance_past_ray(s, &bad_state, -0.5, &bad, &out) ==
|
||||||
GEODESIC_ADVANCE_FAILED &&
|
GEODESIC_ADVANCE_FAILED &&
|
||||||
out.reason == RAY_REASON_PROTOCOL_ERROR,
|
out.reason == RAY_REASON_INVALID_STEPPER_CONFIG,
|
||||||
"inverted step bounds rejected");
|
"inverted step bounds rejected");
|
||||||
bad = dp_config(1e-9, 0.5, 1.0e6);
|
bad = dp_config(1e-9, 0.5, 1.0e6);
|
||||||
bad.consecutive_rejection_limit = 0;
|
bad.consecutive_rejection_limit = 0;
|
||||||
CHECK(geodesic_advance_past_ray(s, &bad_state, -0.5, &bad, &out) ==
|
CHECK(geodesic_advance_past_ray(s, &bad_state, -0.5, &bad, &out) ==
|
||||||
GEODESIC_ADVANCE_FAILED &&
|
GEODESIC_ADVANCE_FAILED &&
|
||||||
out.reason == RAY_REASON_PROTOCOL_ERROR,
|
out.reason == RAY_REASON_INVALID_STEPPER_CONFIG,
|
||||||
"zero reject limit rejected");
|
"zero reject limit rejected");
|
||||||
spacetime_free_slab(s);
|
spacetime_free_slab(s);
|
||||||
}
|
}
|
||||||
@@ -669,7 +669,7 @@ static void test_fixture_fatal_and_bounds(void) {
|
|||||||
geodesic_advance_past_ray(slab, &state, -0.5, &config, &out);
|
geodesic_advance_past_ray(slab, &state, -0.5, &config, &out);
|
||||||
spacetime_free_slab(slab);
|
spacetime_free_slab(slab);
|
||||||
CHECK(r == GEODESIC_ADVANCE_FAILED &&
|
CHECK(r == GEODESIC_ADVANCE_FAILED &&
|
||||||
out.reason == RAY_REASON_PROTOCOL_ERROR,
|
out.reason == RAY_REASON_METRIC_INTERNAL_ERROR,
|
||||||
"stage INTERNAL_ERROR is a direct protocol failure");
|
"stage INTERNAL_ERROR is a direct protocol failure");
|
||||||
CHECK(state.rhs_evaluations == 2u, "internal error not retried");
|
CHECK(state.rhs_evaluations == 2u, "internal error not retried");
|
||||||
}
|
}
|
||||||
@@ -694,7 +694,7 @@ static void test_fixture_fatal_and_bounds(void) {
|
|||||||
geodesic_advance_past_ray(slab, &state, -10.0, &hmin, &out);
|
geodesic_advance_past_ray(slab, &state, -10.0, &hmin, &out);
|
||||||
spacetime_free_slab(slab);
|
spacetime_free_slab(slab);
|
||||||
CHECK(r == GEODESIC_ADVANCE_FAILED &&
|
CHECK(r == GEODESIC_ADVANCE_FAILED &&
|
||||||
out.reason == RAY_REASON_INTEGRATION_ERROR,
|
out.reason == RAY_REASON_MIN_STEP_REACHED,
|
||||||
"step below hmin is an integration error");
|
"step below hmin is an integration error");
|
||||||
CHECK(state.rejected_steps >= 1u, "hmin path rejected before failing");
|
CHECK(state.rejected_steps >= 1u, "hmin path rejected before failing");
|
||||||
|
|
||||||
@@ -1786,7 +1786,7 @@ static void test_unknown_stepper_rejected(void) {
|
|||||||
const RayEndpoint traced = geodesic_trace_past(
|
const RayEndpoint traced = geodesic_trace_past(
|
||||||
&source, &observer, (double[]){1.0, 0.0, 0.0}, &config);
|
&source, &observer, (double[]){1.0, 0.0, 0.0}, &config);
|
||||||
CHECK(traced.outcome == RAY_OUTCOME_INCOMPLETE &&
|
CHECK(traced.outcome == RAY_OUTCOME_INCOMPLETE &&
|
||||||
traced.reason == RAY_REASON_PROTOCOL_ERROR,
|
traced.reason == RAY_REASON_UNKNOWN_STEPPER,
|
||||||
"trace_past rejects an unknown stepper");
|
"trace_past rejects an unknown stepper");
|
||||||
|
|
||||||
GeodesicRayState state;
|
GeodesicRayState state;
|
||||||
@@ -1794,7 +1794,7 @@ static void test_unknown_stepper_rejected(void) {
|
|||||||
const RayEndpoint resumed =
|
const RayEndpoint resumed =
|
||||||
geodesic_trace_past_from_state(&source, &state, &config);
|
geodesic_trace_past_from_state(&source, &state, &config);
|
||||||
CHECK(resumed.outcome == RAY_OUTCOME_INCOMPLETE &&
|
CHECK(resumed.outcome == RAY_OUTCOME_INCOMPLETE &&
|
||||||
resumed.reason == RAY_REASON_PROTOCOL_ERROR,
|
resumed.reason == RAY_REASON_UNKNOWN_STEPPER,
|
||||||
"trace_past_from_state rejects an unknown stepper");
|
"trace_past_from_state rejects an unknown stepper");
|
||||||
|
|
||||||
MetricSlab *slab = NULL;
|
MetricSlab *slab = NULL;
|
||||||
@@ -1805,7 +1805,7 @@ static void test_unknown_stepper_rejected(void) {
|
|||||||
spacetime_free_slab(slab);
|
spacetime_free_slab(slab);
|
||||||
CHECK(r == GEODESIC_ADVANCE_FAILED &&
|
CHECK(r == GEODESIC_ADVANCE_FAILED &&
|
||||||
advanced.outcome == RAY_OUTCOME_INCOMPLETE &&
|
advanced.outcome == RAY_OUTCOME_INCOMPLETE &&
|
||||||
advanced.reason == RAY_REASON_PROTOCOL_ERROR,
|
advanced.reason == RAY_REASON_UNKNOWN_STEPPER,
|
||||||
"advance rejects an unknown stepper");
|
"advance rejects an unknown stepper");
|
||||||
spacetime_destroy(&source);
|
spacetime_destroy(&source);
|
||||||
}
|
}
|
||||||
@@ -2041,6 +2041,35 @@ static void test_event_step_time_precision(void) {
|
|||||||
fabs(null_residual(&metric, state.Pi) - 1.0) < 1e-12,
|
fabs(null_residual(&metric, state.Pi) - 1.0) < 1e-12,
|
||||||
"time-precision fixture stays null");
|
"time-precision fixture stays null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* At a huge coordinate-time origin a nominal step below the local ULP
|
||||||
|
* cannot define a representable nonzero interval: report the exact
|
||||||
|
* TIME_STEP_UNREPRESENTABLE cause instead of committing a zero-length step. */
|
||||||
|
{
|
||||||
|
const double origin = 1.0e17;
|
||||||
|
FixtureContext c;
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
c.radius = 1.0e30;
|
||||||
|
SpacetimeSource source = {.ops = &fixture_ops, .context = &c};
|
||||||
|
GeodesicRayState state;
|
||||||
|
memset(&state, 0, sizeof state);
|
||||||
|
state.coordinate_time = origin;
|
||||||
|
state.integration_start_time = origin;
|
||||||
|
state.Pi[0] = -1.0;
|
||||||
|
GeodesicTraceConfig config = dp_config(1e-9, 1e-12, 1.0e30);
|
||||||
|
config.min_step = 1e-12;
|
||||||
|
config.max_step = 1.0;
|
||||||
|
MetricSlab *slab = NULL;
|
||||||
|
CHECK(spacetime_load_slab(&source, origin, origin - 1.0e30, &slab) == 0,
|
||||||
|
"unrepresentable slab");
|
||||||
|
RayEndpoint out = blank_endpoint();
|
||||||
|
const GeodesicAdvanceResult r =
|
||||||
|
geodesic_advance_past_ray(slab, &state, origin - 1.0e30, &config, &out);
|
||||||
|
spacetime_free_slab(slab);
|
||||||
|
CHECK(r == GEODESIC_ADVANCE_FAILED &&
|
||||||
|
out.reason == RAY_REASON_TIME_STEP_UNREPRESENTABLE,
|
||||||
|
"sub-ULP step is unrepresentable");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A crossing whose dense root is absorbed at theta == 1 (F_after strictly
|
/* A crossing whose dense root is absorbed at theta == 1 (F_after strictly
|
||||||
@@ -2099,8 +2128,10 @@ static void test_event_boundary_roundoff_exit(void) {
|
|||||||
"boundary roundoff start escapes outward");
|
"boundary roundoff start escapes outward");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A single-end state clearly outside its worldtube is a protocol error, not an
|
/* A single-end state clearly outside its worldtube is reported with the exact
|
||||||
* unbounded search that can only end as budget exhaustion. */
|
* OUTSIDE_WORLDTUBE cause, not an unbounded search that can only end as budget
|
||||||
|
* exhaustion, and the last trusted accepted state plus the real cost counters
|
||||||
|
* are preserved (the failed event step is never committed). */
|
||||||
static void test_event_clearly_outside_protocol(void) {
|
static void test_event_clearly_outside_protocol(void) {
|
||||||
FixtureContext c;
|
FixtureContext c;
|
||||||
memset(&c, 0, sizeof c);
|
memset(&c, 0, sizeof c);
|
||||||
@@ -2121,8 +2152,15 @@ static void test_event_clearly_outside_protocol(void) {
|
|||||||
spacetime_free_slab(slab);
|
spacetime_free_slab(slab);
|
||||||
CHECK(r == GEODESIC_ADVANCE_FAILED &&
|
CHECK(r == GEODESIC_ADVANCE_FAILED &&
|
||||||
out.outcome == RAY_OUTCOME_INCOMPLETE &&
|
out.outcome == RAY_OUTCOME_INCOMPLETE &&
|
||||||
out.reason == RAY_REASON_PROTOCOL_ERROR,
|
out.reason == RAY_REASON_OUTSIDE_WORLDTUBE,
|
||||||
"clearly-outside single end is a protocol error");
|
"clearly-outside single end is an explicit failure");
|
||||||
|
CHECK(out.stop_coordinate_time == 0.0 && out.final_x[0] == 2.0 &&
|
||||||
|
out.final_x[1] == 0.0 && out.final_x[2] == 0.0 &&
|
||||||
|
out.accepted_steps == 0u,
|
||||||
|
"outside-worldtube failure preserves the last trusted state");
|
||||||
|
CHECK(state.coordinate_time == 0.0 && state.x[0] == 2.0 &&
|
||||||
|
state.steps == 0u && state.rhs_evaluations > 0ul,
|
||||||
|
"outside-worldtube failure keeps the trusted state and real cost");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Escape and threshold are both localized on the actual trajectory and ordered
|
/* Escape and threshold are both localized on the actual trajectory and ordered
|
||||||
@@ -2529,7 +2567,7 @@ static void test_event_threshold_confirmation_exhausted(void) {
|
|||||||
spacetime_free_slab(slab);
|
spacetime_free_slab(slab);
|
||||||
CHECK(r == GEODESIC_ADVANCE_FAILED &&
|
CHECK(r == GEODESIC_ADVANCE_FAILED &&
|
||||||
out.outcome == RAY_OUTCOME_INCOMPLETE &&
|
out.outcome == RAY_OUTCOME_INCOMPLETE &&
|
||||||
out.reason == RAY_REASON_INTEGRATION_ERROR,
|
out.reason == RAY_REASON_THRESHOLD_EVENT_UNCONFIRMED,
|
||||||
"unconfirmable threshold is an incomplete integration failure");
|
"unconfirmable threshold is an incomplete integration failure");
|
||||||
CHECK(out.outcome != RAY_OUTCOME_DARK,
|
CHECK(out.outcome != RAY_OUTCOME_DARK,
|
||||||
"unconfirmable threshold is not rewritten into DARK");
|
"unconfirmable threshold is not rewritten into DARK");
|
||||||
@@ -2539,6 +2577,35 @@ static void test_event_threshold_confirmation_exhausted(void) {
|
|||||||
CHECK(out.rhs_evaluations > 0ul, "actual RHS cost is recorded");
|
CHECK(out.rhs_evaluations > 0ul, "actual RHS cost is recorded");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A pure error-estimate rejection that exhausts the consecutive-rejection quota
|
||||||
|
* reports the exact REJECTION_LIMIT cause. With a one-rejection quota the
|
||||||
|
* limit is reached before any minimum-step check, so this is not MIN_STEP. */
|
||||||
|
static void test_rejection_limit_reason(void) {
|
||||||
|
AlphaContext c;
|
||||||
|
memset(&c, 0, sizeof c);
|
||||||
|
c.k = 1.0;
|
||||||
|
c.radius = 10.0;
|
||||||
|
SpacetimeSource source = {.ops = &alpha_ops, .context = &c};
|
||||||
|
GeodesicRayState state;
|
||||||
|
alpha_state(0.2, c.k, &state);
|
||||||
|
GeodesicTraceConfig config = dp_config(1e-15, 1.0, 10.0);
|
||||||
|
config.max_step = 1.0;
|
||||||
|
config.min_step = 1.0;
|
||||||
|
config.consecutive_rejection_limit = 1;
|
||||||
|
MetricSlab *slab = NULL;
|
||||||
|
CHECK(spacetime_load_slab(&source, 0.0, -5.0, &slab) == 0,
|
||||||
|
"reject-limit slab");
|
||||||
|
RayEndpoint out = blank_endpoint();
|
||||||
|
const GeodesicAdvanceResult r =
|
||||||
|
geodesic_advance_past_ray(slab, &state, -5.0, &config, &out);
|
||||||
|
spacetime_free_slab(slab);
|
||||||
|
CHECK(r == GEODESIC_ADVANCE_FAILED &&
|
||||||
|
out.reason == RAY_REASON_REJECTION_LIMIT,
|
||||||
|
"error-estimate rejection quota reports REJECTION_LIMIT");
|
||||||
|
CHECK(state.coordinate_time == 0.0 && state.rejected_steps >= 1u,
|
||||||
|
"rejected trial did not advance the accepted state");
|
||||||
|
}
|
||||||
|
|
||||||
/* Uniform unit scaling: with every length/time quantity scaled by 1e-9 the
|
/* Uniform unit scaling: with every length/time quantity scaled by 1e-9 the
|
||||||
* same flat escape must give event time/scale ~ 3 and x/scale ~ 5, i.e. no
|
* same flat escape must give event time/scale ~ 3 and x/scale ~ 5, i.e. no
|
||||||
* absolute coordinate-time floor breaks small units. */
|
* absolute coordinate-time floor breaks small units. */
|
||||||
@@ -2611,6 +2678,7 @@ int main(void) {
|
|||||||
test_event_worldtube_polynomial_time_origin();
|
test_event_worldtube_polynomial_time_origin();
|
||||||
test_event_threshold_closed_endpoint();
|
test_event_threshold_closed_endpoint();
|
||||||
test_event_threshold_confirmation_exhausted();
|
test_event_threshold_confirmation_exhausted();
|
||||||
|
test_rejection_limit_reason();
|
||||||
test_event_unit_scaling();
|
test_event_unit_scaling();
|
||||||
if (failures != 0) {
|
if (failures != 0) {
|
||||||
fprintf(stderr, "geodesic adaptive regression: %d failure(s)\n", failures);
|
fprintf(stderr, "geodesic adaptive regression: %d failure(s)\n", failures);
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ refusal must not recommend a larger retry budget for a pure integration error.
|
|||||||
|
|
||||||
A deterministic DP54 integration error is produced without a large workload:
|
A deterministic DP54 integration error is produced without a large workload:
|
||||||
strict tolerances, ``min == initial == max`` step, and one allowed rejection
|
strict tolerances, ``min == initial == max`` step, and one allowed rejection
|
||||||
make the first trial step fail as ``INTEGRATION_ERROR``. Small 16x8 scenes
|
make the first trial step fail. The rejection quota is reached before any
|
||||||
keep every case short. Two-frame movie coverage uses the existing
|
minimum-step check, so the exact detailed reason is ``REJECTION_LIMIT``. Small
|
||||||
|
16x8 scenes keep every case short. Two-frame movie coverage uses the existing
|
||||||
``test_observer_<backend>`` fixture track duplicated to two rows (the
|
``test_observer_<backend>`` fixture track duplicated to two rows (the
|
||||||
Schwarzschild metric is stationary, so the same tetrad is valid at both
|
Schwarzschild metric is stationary, so the same tetrad is valid at both
|
||||||
times); the test then checks that each failing sample is counted once across
|
times); the test then checks that each failing sample is counted once across
|
||||||
@@ -48,9 +49,10 @@ COMMON = ['--catalog', 'assets/sky_grid_5deg.csv', '--width', 16, '--height', 8,
|
|||||||
'--fov-deg', 80, '--exposure', 1e-3, '--coarse-cell-pixels', 8,
|
'--fov-deg', 80, '--exposure', 1e-3, '--coarse-cell-pixels', 8,
|
||||||
'--refine-max-level', 0, '--psf-relative-tail', 1e-4]
|
'--refine-max-level', 0, '--psf-relative-tail', 1e-4]
|
||||||
|
|
||||||
# Deterministic DP54 integration failure: tight tolerances, a single fixed step
|
# Deterministic DP54 rejection-quota failure: tight tolerances, a single fixed
|
||||||
# bound, and one allowed rejection. Every ray rejects its first trial step and
|
# step bound, and one allowed rejection. Every ray rejects its first trial step
|
||||||
# reports INTEGRATION_ERROR instead of a fabricated terminal category.
|
# and reports the exact REJECTION_LIMIT reason instead of a fabricated terminal
|
||||||
|
# category.
|
||||||
INJECT = ['--integrator', 'dp54',
|
INJECT = ['--integrator', 'dp54',
|
||||||
'--ode-rtol', '1e-15', '--ode-atol-x', '1e-15',
|
'--ode-rtol', '1e-15', '--ode-atol-x', '1e-15',
|
||||||
'--ode-atol-pi', '1e-15', '--ode-atol-l', '1e-15',
|
'--ode-atol-pi', '1e-15', '--ode-atol-l', '1e-15',
|
||||||
@@ -139,7 +141,7 @@ with tempfile.TemporaryDirectory(prefix='gr-ray-diagnostics-',
|
|||||||
'--output', single)
|
'--output', single)
|
||||||
err = single_run.stderr
|
err = single_run.stderr
|
||||||
assert 'Ray failures:' in err, err
|
assert 'Ray failures:' in err, err
|
||||||
assert 'INCOMPLETE=' in err and 'INTEGRATION_ERROR' in err, err
|
assert 'INCOMPLETE=' in err and 'REJECTION_LIMIT' in err, err
|
||||||
assert frame_reported(err, 0), err
|
assert frame_reported(err, 0), err
|
||||||
if not is_debug(single_run):
|
if not is_debug(single_run):
|
||||||
assert 'ray failure:' not in err, err
|
assert 'ray failure:' not in err, err
|
||||||
@@ -154,7 +156,7 @@ with tempfile.TemporaryDirectory(prefix='gr-ray-diagnostics-',
|
|||||||
'--output', verbose)
|
'--output', verbose)
|
||||||
err = verbose_run.stderr
|
err = verbose_run.stderr
|
||||||
assert 'ray failure:' in err, err
|
assert 'ray failure:' in err, err
|
||||||
assert 'reason=INTEGRATION_ERROR' in err, err
|
assert 'reason=REJECTION_LIMIT' in err, err
|
||||||
assert 'vertex=' in err and 'film=(' in err, err
|
assert 'vertex=' in err and 'film=(' in err, err
|
||||||
assert 'accepted=' in err and 'rejected=' in err and 'rhs=' in err, err
|
assert 'accepted=' in err and 'rejected=' in err and 'rhs=' in err, err
|
||||||
# Bounded detail: at most RAY_DIAG_REPS_PER_REASON per reason, remainder
|
# Bounded detail: at most RAY_DIAG_REPS_PER_REASON per reason, remainder
|
||||||
@@ -241,7 +243,7 @@ with tempfile.TemporaryDirectory(prefix='gr-ray-diagnostics-',
|
|||||||
'--output', tmp / f'map_refused.{ext}', ok=False)
|
'--output', tmp / f'map_refused.{ext}', ok=False)
|
||||||
err = refused.stderr
|
err = refused.stderr
|
||||||
assert frame_reported(err, 0) and frame_reported(err, 1), err
|
assert frame_reported(err, 0) and frame_reported(err, 1), err
|
||||||
assert 'INTEGRATION_ERROR' in err, err
|
assert 'REJECTION_LIMIT' in err, err
|
||||||
assert 'phase=import' in err, err
|
assert 'phase=import' in err, err
|
||||||
assert 'Incomplete render refused' in err, err
|
assert 'Incomplete render refused' in err, err
|
||||||
assert err.index('frame 0') < err.index('Incomplete render refused'), err
|
assert err.index('frame 0') < err.index('Incomplete render refused'), err
|
||||||
@@ -255,7 +257,7 @@ with tempfile.TemporaryDirectory(prefix='gr-ray-diagnostics-',
|
|||||||
'--verbose', '--output', replay)
|
'--verbose', '--output', replay)
|
||||||
err = replay_run.stderr
|
err = replay_run.stderr
|
||||||
assert 'Ray failures:' in err and frame_reported(err, 0), err
|
assert 'Ray failures:' in err and frame_reported(err, 0), err
|
||||||
assert 'INTEGRATION_ERROR' in err and 'phase=import' in err, err
|
assert 'REJECTION_LIMIT' in err and 'phase=import' in err, err
|
||||||
assert 'stop_t=' not in err and 'trusted=1' not in err, err
|
assert 'stop_t=' not in err and 'trusted=1' not in err, err
|
||||||
|
|
||||||
# 6) Budget exhaustion is a distinct, always-on message, and its refusal
|
# 6) Budget exhaustion is a distinct, always-on message, and its refusal
|
||||||
|
|||||||
Reference in new issue
Block a user