Commit 0b385735 authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: 3-C rung 2 - 15-arg peak contract in the lean oracle, FZ-norm in...

CLAUDE: 3-C rung 2 - 15-arg peak contract in the lean oracle, FZ-norm in place, pose_corr checkpoint-2 export

Andrey's ruling 07/13/2026 (frozen GPU contract = 15-arg getMaxXYCmEig
superset; recentering runtime knob; de-bias optional, default off):

- CuasRtParameters: NEW 'CUAS RT' knobs pose_recenter (recentering passes,
  0 = validated single-pass baseline) + pose_debias (window-autocorr envelope
  de-bias fraction, 0 = off); corr-xml _curt_pose_recenter/_curt_pose_debias.
- CuasPoseRT.leanMeasure: peak measurement switched to the 15-arg
  getMaxXYCmEig (eig_fast2x2=true = the contract's closed-form eigen);
  windowAutocorrDebias() = the single source of the de-bias array (Java
  oracle, export case, JNA host).
- TDCorrTile.convertTDtoPDInPlace: corr2D_normalize runs on the inter corr
  TD buffer where execCorr2D_inter_TD left it (NULL weights == the repack
  path's uniform 1.0 -> bit-identical PD); the TD D2H->repack->H2D round
  trip is gone from the lean chain on both backends.
- PoseCorrExport: checkpoint 2 in the same case - expected_corr_pd
  (GPU-produced normalized PD, the tol-0 target for the in-place replay),
  expected_peaks (double Java oracle incl. weak-peak rejects as NaN rows),
  contract params + optional debias array in the manifest; compareResults
  round-trips corr_pd/peaks when the C++ test writes them.

mvn -DskipTests clean package OK; C++ side: tile_processor_gpu same-day
commit (corr2D_peak_eig + tests, synthetic gate PASS, regressions PASS).
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent 18be472c
......@@ -431,30 +431,52 @@ public class CuasPoseRT {
// pose_corr capture: checkpoint 1 = the raw TD correlation + packed indices right
// here, BEFORE fat-zero normalization (rung 2's boundary). By Claude on 07/13/2026.
PoseCorrExport.iterCorr(gpuQuad, scene, col_weights);
// 5. TD tiles -> FZ-normalize -> pixel domain (the JNA-validated TDCorrTile path)
final TDCorrTile [] td_tiles = TDCorrTile.getFromGpu(gpuQuad);
final double [][] corr_pd = TDCorrTile.convertTDtoPD(
// 5. FZ-normalize IN PLACE (rung 2 first half, 07/13/2026): corr2D_normalize runs on
// the TD buffer where execCorr2D_inter_TD left it - the old getFromGpu/convertTDtoPD
// TD D2H -> repack -> H2D round trip is gone (PD result bit-identical: per-tile
// independent kernel, uniform weight 1.0 == null weights - see convertTDtoPDInPlace).
// The PD D2H below is the LAST remaining bulk transfer - it goes away with the
// rung-2 peak kernel. By Claude on 07/13/2026 (was the TDCorrTile round-trip path).
final double [][] corr_pd = TDCorrTile.convertTDtoPDInPlace(
gpuQuad,
td_tiles,
0xFE, // corr_type (sum slot, as CuasMotion)
clt_parameters.curt.fz_inter, // RT-decoupled INTER fat zero (was erroneously the INTRA getGpuFatZero) // By Claude on 07/05/2026
debugLevel);
if (corr_pd == null) {
System.out.println("leanMeasure(): convertTDtoPD returned null for scene "+scene.getImageName());
return null;
}
// pose_corr capture: checkpoint 2 = the FZ-normalized PD (in-place-normalize tol-0
// target) + the frozen 15-arg peak-contract oracle on every PD tile. Armed-only no-op.
// By Claude on 07/13/2026.
PoseCorrExport.iterPd(clt_parameters, gpuQuad, scene, corr_pd);
if (corr_pd_out != null) corr_pd_out[0] = corr_pd; // debug rendering (-POSE-RT-CORR2D) // By Claude on 07/04/2026
// (img_out is rendered in step 3, BEFORE the sum scaling - the true weighted average) // By Claude on 07/04/2026
// 6. peak + eigen per tile (the GPU argmax+eigen kernel's Java oracle)
// 6. peak + eigen per tile (the GPU argmax+eigen kernel's Java oracle).
// FROZEN RUNG-2 GPU CONTRACT (Andrey's ruling 07/13/2026): the 15-arg
// getMaxXYCmEig superset - closed-form 2x2 eigen (getEigen2x2, the GPU-portable
// path) + recentering as a RUNTIME parameter (curt.pose_recenter, 0 = the
// validated single-pass baseline) + optional envelope de-bias as a precomputed
// multiplicative array (curt.pose_debias, 0 = off; the kernel takes the array
// pointer, NULL = off). Oracle and kernel share this exact shape so the
// checkpoint-2 comparison is contract-vs-contract. By Claude on 07/13/2026.
final int corr_width = 2 * GPUTileProcessorDttSize() - 1;
final double [] debias = (clt_parameters.curt.pose_debias > 0) ?
windowAutocorrDebias(GPUTileProcessorDttSize(), clt_parameters.curt.pose_debias) : null;
final double [][] centers = new double [num_pix][];
final double [][] vector_XYS = new double [num_pix][];
final double [][] eigen = new double [num_pix][];
int num_meas = 0;
for (int i = 0; i < num_pix; i++) {
if ((i >= corr_pd.length) || (corr_pd[i] == null) || (pXpYD_center[i] == null)) continue;
double [] corr_tile = corr_pd[i]; // not modified by getMaxXYCmEig (fpn_mask == null)
if (debias != null) { // per-tile copy: corr_pd stays raw for -POSE-RT-CORR2D // By Claude on 07/13/2026
corr_tile = new double [corr_pd[i].length];
for (int k = 0; k < corr_tile.length; k++) {
corr_tile[k] = corr_pd[i][k] * debias[k];
}
}
final double [] r = Correlation2d.getMaxXYCmEig(
corr_pd[i], // will not be modified (fpn_mask == null)
corr_tile,
corr_width,
clt_parameters.imp.eig_min_abs, // abs_min
clt_parameters.imp.eig_min_rel, // rel_min
......@@ -463,9 +485,14 @@ public class CuasPoseRT {
// eigen lambdas -> sqrt(l) hits eig_max_sqrt -> setEigenTransform
// k=max(0,1/sqrt(l)-1/max) ZEROES wide-peak tiles (the eigen-ON
// 3x slow convergence). By Claude on 07/12/2026, Andrey's go.
clt_parameters.curt.pose_recenter,// recentering passes (0 = baseline single-pass) // By Claude on 07/13/2026
clt_parameters.imp.eig_sub_frac1, // pedestal suppression during recentering
clt_parameters.imp.eig_scale_axes,// recenter window: scale eigen-ellipse half-axes
clt_parameters.imp.eig_inc_axes, // recenter window: add to half-axes, pix
null, // fpn_mask (input is FPN-subtracted)
false, // ignore_border
null, // debug_data
true, // eig_fast2x2 FROZEN true: the closed-form eigen IS the contract
false); // debug
if (r == null) continue;
centers[i] = pXpYD_center[i];
......@@ -484,6 +511,47 @@ public class CuasPoseRT {
return com.elphel.imagej.gpu.GPUTileProcessor.DTT_SIZE;
}
/**
* Precomputed envelope de-bias array for the lean peak measurement (rung-2 optional
* hook, Andrey's ruling 07/13/2026 - default OFF). The pixel-domain correlation of two
* MCLT-windowed tiles is modulated by the window AUTOCORRELATION envelope (separable,
* peaked at center): a displaced peak rides an envelope that decays away from center,
* so its centroid is pulled back toward zero - the CM argmax under-reports offsets by
* ~0.75-0.9x (probed 07/13; the estimator itself is unbiased, the envelope is the bias).
* De-bias = multiply the correlation by envelope^-frac before peak extraction.
* The GPU kernel takes the SAME array as an optional pointer (NULL = off); this is the
* single source of the array for the Java oracle, the export case, and the JNA host.
* @param transform_size DTT size (8): window = sin(pi/(2*ts)*(i+0.5)), 2*ts samples
* @param frac exponent fraction: 0 = all-ones (no-op), 1 = full envelope divide
* @return [(2*ts-1)^2] multiplicative de-bias, 1.0 at the center pixel
* By Claude on 07/13/2026.
*/
public static double [] windowAutocorrDebias(final int transform_size, final double frac) {
final int wnd_len = 2 * transform_size; // MCLT window length (16)
final int corr_width = 2 * transform_size - 1; // correlation tile side (15)
final double [] wnd = new double [wnd_len];
for (int i = 0; i < wnd_len; i++) {
wnd[i] = Math.sin(Math.PI * (i + 0.5) / wnd_len);
}
final double [] acorr = new double [corr_width]; // 1D autocorr, index d+ (ts-1), d = -(ts-1)..(ts-1)
for (int d = -(transform_size - 1); d <= (transform_size - 1); d++) {
double s = 0.0;
for (int i = Math.max(0, -d); i < Math.min(wnd_len, wnd_len - d); i++) {
s += wnd[i] * wnd[i + d];
}
acorr[d + transform_size - 1] = s;
}
final double center = acorr[transform_size - 1]; // normalize so the center is 1.0
final double [] debias = new double [corr_width * corr_width];
for (int iy = 0; iy < corr_width; iy++) {
for (int ix = 0; ix < corr_width; ix++) {
final double env = (acorr[iy] / center) * (acorr[ix] / center); // separable 2D envelope
debias[iy * corr_width + ix] = Math.pow(env, -frac);
}
}
return debias;
}
/**
* Deterministic task-stream order (3-A3, GPU static shape): the setInterTasks*
* builders fill their output from worker threads (aTiles.getAndIncrement()), so the
......
......@@ -17,6 +17,16 @@
** correlate2D_inter - convert_direct and the consolidation chain are already
** covered by test_convert_direct / test_avg_td_oob).
**
** CHECKPOINT 2 (rung 2, added 07/13/2026): the same case also carries the
** FZ-normalized pixel-domain correlation (expected_corr_pd_it<k> - the tol-0
** target for the IN-PLACE corr2D_normalize replay, GPU-produced floats) and
** the frozen 15-arg getMaxXYCmEig peak-contract oracle (expected_peaks_it<k>,
** {dx,dy,strength,e0x,e0y,l0,l1} per PD tile, NaN row = weak-peak reject;
** double-precision Java = the epsilon tier for the float GPU kernel). Contract
** params (eig_*, pose_recenter, pose_debias + the optional debias array) are
** in the manifest - Andrey's ruling 07/13/2026: closed-form 2x2 eigen,
** recentering and envelope de-bias as runtime parameters.
**
** CAPTURE MODEL: static armed-capture hooks called from the PRODUCTION lean
** chain (CuasPoseRT.leanMeasure / leanFitScene, CuasConditioning ingest).
** All hooks are no-ops unless arm() was called (the curt.kernel_test
......@@ -91,6 +101,18 @@ public class PoseCorrExport {
private static final ArrayList<int []> corr_indices = new ArrayList<int []>(); // packed (tile<<8)|sensor, 0xff=sum
private static final ArrayList<float []> corr_td = new ArrayList<float []>(); // raw TD, [n_corr*256]
// checkpoint 2 (rung 2): FZ-normalized pixel-domain correlation + peak oracle // By Claude on 07/13/2026
private static final ArrayList<int []> pd_indices = new ArrayList<int []>(); // dense tile index per PD tile
private static final ArrayList<float []> pd_data = new ArrayList<float []>(); // normalized PD, [n_pd*225]
private static final ArrayList<float []> peaks = new ArrayList<float []>(); // oracle {dx,dy,str,e0x,e0y,l0,l1}, NaN row = rejected
private static double fz_inter, corr_weight; // execCorr2D_normalize inputs
private static double eig_min_abs, eig_min_rel, min_str_sum, eig_sub_frac; // frozen peak-contract params
private static int pose_recenter; // recentering passes (runtime contract param)
private static double eig_sub_frac1, eig_scale_axes, eig_inc_axes, pose_debias;
private static float [] debias_arr = null; // envelope de-bias (only when pose_debias > 0)
private static double [] debias_oracle = null; // same, double - the oracle multiplies this
private static boolean pd_statics_ok = false;
/** Arm the capture (CuasRT.kernelTest "pose_corr"). One case per arm. */
public static synchronized void arm(String dir, int debugLevel) {
disarm();
......@@ -118,6 +140,10 @@ public class PoseCorrExport {
tasks0_pre.clear(); tasks1_pre.clear();
tasks0_post.clear(); tasks1_post.clear();
corr_indices.clear(); corr_td.clear();
pd_indices.clear(); pd_data.clear(); peaks.clear(); // By Claude on 07/13/2026
debias_arr = null;
debias_oracle = null;
pd_statics_ok = false;
}
/**
......@@ -211,6 +237,81 @@ public class PoseCorrExport {
corr_td.add(gpuQuad.getCorrTdData());
}
/**
* Hook (CuasPoseRT.leanMeasure, right after TDCorrTile.convertTDtoPD): CHECKPOINT 2
* (rung 2) expected data. Two comparison tiers in one capture:
* - expected_corr_pd: the FZ-normalized pixel-domain correlation exactly as the GPU
* produced it (corr_pd doubles are exact copies of the getCorr2D floats, so the
* cast back is bit-exact) - the tol-0 target for the IN-PLACE normalize replay
* (corr2D_normalize math is per-tile, position-independent);
* - expected_peaks: the frozen 15-arg getMaxXYCmEig contract (Andrey's ruling
* 07/13/2026: closed-form 2x2 eigen, recentering + envelope de-bias as runtime
* params) run HERE on every non-null PD tile - the double-precision oracle tier
* for the new argmax/centroid/eigen kernel (float kernel compares at an epsilon;
* the tol-0 verdict for it comes later from NVRTC production capture, rung-1 rule).
* Runs the oracle on ALL PD tiles (not only pXpYD-valid ones) so the kernel's
* reject path (weak peak -> NaN row) is exercised. By Claude on 07/13/2026.
*/
public static synchronized void iterPd(
CLTParameters clt_parameters,
GpuQuad gpuQuad,
QuadCLT scene,
double [][] corr_pd) {
if (!armed || done || (bound_scene == null) || !bound_scene.equals(scene.getImageName())) return;
final int corr_width = 2 * GPUTileProcessor.DTT_SIZE - 1;
if (!pd_statics_ok) {
fz_inter = clt_parameters.curt.fz_inter;
corr_weight = gpuQuad.getNumCamsInter(); // TDCorrTile.getFromGpu weight = index_combo = num pairs - 1
eig_min_abs = clt_parameters.imp.eig_min_abs;
eig_min_rel = clt_parameters.imp.eig_min_rel;
min_str_sum = clt_parameters.imp.min_str_sum;
eig_sub_frac = clt_parameters.imp.eig_sub_frac;
pose_recenter = clt_parameters.curt.pose_recenter;
eig_sub_frac1 = clt_parameters.imp.eig_sub_frac1;
eig_scale_axes= clt_parameters.imp.eig_scale_axes;
eig_inc_axes = clt_parameters.imp.eig_inc_axes;
pose_debias = clt_parameters.curt.pose_debias;
if (pose_debias > 0) {
debias_oracle = com.elphel.imagej.cuas.rt.CuasPoseRT.windowAutocorrDebias(
GPUTileProcessor.DTT_SIZE, pose_debias);
debias_arr = new float [debias_oracle.length];
for (int i = 0; i < debias_oracle.length; i++) debias_arr[i] = (float) debias_oracle[i];
}
pd_statics_ok = true;
}
int n_pd = 0;
for (int i = 0; i < corr_pd.length; i++) if (corr_pd[i] != null) n_pd++;
final int [] indices = new int [n_pd];
final float [] pd = new float [n_pd * corr_width * corr_width];
final float [] pk = new float [n_pd * 7];
int np = 0;
for (int i = 0; i < corr_pd.length; i++) if (corr_pd[i] != null) {
indices[np] = i;
for (int k = 0; k < corr_pd[i].length; k++) {
pd[np * corr_width * corr_width + k] = (float) corr_pd[i][k];
}
double [] corr_tile = corr_pd[i];
if (debias_oracle != null) {
corr_tile = new double [corr_pd[i].length];
for (int k = 0; k < corr_tile.length; k++) corr_tile[k] = corr_pd[i][k] * debias_oracle[k];
}
final double [] r = com.elphel.imagej.tileprocessor.Correlation2d.getMaxXYCmEig(
corr_tile, corr_width,
eig_min_abs, eig_min_rel, min_str_sum, eig_sub_frac,
pose_recenter, eig_sub_frac1, eig_scale_axes, eig_inc_axes,
null, false, null,
true, // eig_fast2x2 FROZEN true - the contract's closed-form eigen
false);
for (int k = 0; k < 7; k++) {
pk[np * 7 + k] = (r == null) ? Float.NaN : (float) r[k];
}
np++;
}
pd_indices.add(indices);
pd_data.add(pd);
peaks.add(pk);
}
/**
* Hook (CuasPoseRT.leanFitScene, after the measure<->solve cycles succeeded):
* all iterations of the bound scene are captured - write the case.
......@@ -345,10 +446,16 @@ public class PoseCorrExport {
throw new IOException("incomplete capture: iters pre/post/corr = "+
tasks0_pre.size()+"/"+tasks0_post.size()+"/"+corr_td.size());
}
if (!pd_statics_ok || (pd_indices.size() != tasks0_pre.size())) { // By Claude on 07/13/2026
throw new IOException("incomplete checkpoint-2 capture: pd iters = "+pd_indices.size()+
" vs "+tasks0_pre.size()+" (iterPd hook missing?)");
}
final int num_iters = tasks0_pre.size();
final int chunk = 4 * GPUTileProcessor.DTT_SIZE * GPUTileProcessor.DTT_SIZE; // 256
final int pd_size = (2 * GPUTileProcessor.DTT_SIZE - 1) * (2 * GPUTileProcessor.DTT_SIZE - 1); // 225
try (TestDataWriter tdw = new TestDataWriter(dir_path, "pose_corr test case: scene "+bound_scene+
" ("+num_iters+" LMA iterations, checkpoint 1 = raw TD correlation after correlate2D_inter)")) {
" ("+num_iters+" LMA iterations, checkpoint 1 = raw TD correlation after correlate2D_inter,"+
" checkpoint 2 = FZ-normalized PD + getMaxXYCmEig peak oracle)")) {
tdw.prm("num_sensors", num_sens);
tdw.prm("colors", colors);
tdw.prm("tilesx", tilesx);
......@@ -366,6 +473,18 @@ public class PoseCorrExport {
tdw.prm("scale0", scales3[0]);
tdw.prm("scale1", scales3[1]);
tdw.prm("scale2", scales3[2]);
// checkpoint 2: FZ-normalize + frozen peak contract params // By Claude on 07/13/2026
tdw.prm("fz_inter", fz_inter);
tdw.prm("corr_weight", corr_weight);
tdw.prm("eig_min_abs", eig_min_abs);
tdw.prm("eig_min_rel", eig_min_rel);
tdw.prm("min_str_sum", min_str_sum);
tdw.prm("eig_sub_frac", eig_sub_frac);
tdw.prm("pose_recenter", pose_recenter);
tdw.prm("eig_sub_frac1", eig_sub_frac1);
tdw.prm("eig_scale_axes",eig_scale_axes);
tdw.prm("eig_inc_axes", eig_inc_axes);
tdw.prm("pose_debias", pose_debias);
final float [] images_flat = new float [num_sens * img_h * img_w];
for (int ncam = 0; ncam < num_sens; ncam++) {
System.arraycopy(images[ncam], 0, images_flat, ncam * img_h * img_w, img_h * img_w);
......@@ -386,6 +505,9 @@ public class PoseCorrExport {
tdw.buf("lpf_rb_corr", lpf_rb_corr, lpf_rb_corr.length);
tdw.buf("log_corr", log_corr, log_corr.length);
tdw.buf("clt_ref_cam0", clt_ref0, tilesy, tilesx, colors, chunk);
if (debias_arr != null) { // envelope de-bias array (the kernel's optional pointer) // By Claude on 07/13/2026
tdw.buf("debias", debias_arr, debias_arr.length);
}
for (int k = 0; k < num_iters; k++) {
tdw.buf("ftasks0_it"+k, tasks0_pre.get(k), tasks0_pre.get(k).length / task_size, task_size);
tdw.buf("expected_tasks0_it"+k, tasks0_post.get(k), tasks0_post.get(k).length / task_size, task_size);
......@@ -395,11 +517,18 @@ public class PoseCorrExport {
}
tdw.buf("expected_corr_indices_it"+k, corr_indices.get(k), corr_indices.get(k).length);
tdw.buf("expected_corr_td_it"+k, corr_td.get(k), corr_td.get(k).length / chunk, chunk);
// checkpoint 2: normalized PD (tol-0 target for the in-place FZ-norm replay) +
// the double-precision peak oracle (epsilon tier for the float kernel) // By Claude on 07/13/2026
tdw.buf("expected_pd_indices_it"+k, pd_indices.get(k), pd_indices.get(k).length);
tdw.buf("expected_corr_pd_it"+k, pd_data.get(k), pd_data.get(k).length / pd_size, pd_size);
tdw.buf("expected_peaks_it"+k, peaks.get(k), peaks.get(k).length / 7, 7);
}
if (debug > -3) {
System.out.println("PoseCorrExport: wrote "+tdw.getDir()+" (scene "+bound_scene+", "+
num_iters+" iterations, "+(tasks0_pre.get(0).length / task_size)+" tiles/set, "+
(corr_td.get(0).length / chunk)+" corr tiles/iteration, sel_sensors="+sel_sensors+")");
(corr_td.get(0).length / chunk)+" corr tiles/iteration, "+
pd_indices.get(0).length+" PD/peak tiles/iteration, sel_sensors="+sel_sensors+
", recenter="+pose_recenter+", debias="+pose_debias+")"); // ckpt-2 counts by Claude on 07/13/2026
}
}
}
......@@ -414,13 +543,21 @@ public class PoseCorrExport {
final TestDataReader expected = new TestDataReader(dir);
final TestDataReader results = TestDataReader.results(dir);
double max_diff = 0.0;
for (int k = 0; expected.has("expected_corr_td_it"+k); k++) {
if (!results.has("corr_td_it"+k)) {
System.out.println("PoseCorrExport.compareResults: results/corr_td_it"+k+" missing");
// results the C++ test may write, vs their expected_ counterparts. corr_pd/peaks
// (checkpoint 2) are optional until the rung-2 test lands. By Claude on 07/13/2026.
final String [] pairs = {"corr_td", "corr_pd", "peaks"};
for (String base : pairs) {
if (!expected.has("expected_"+base+"_it0")) continue; // not in this case vintage
for (int k = 0; expected.has("expected_"+base+"_it"+k); k++) {
if (!results.has(base+"_it"+k)) {
if (base.equals("corr_td") || (debugLevel > -3)) {
System.out.println("PoseCorrExport.compareResults: results/"+base+"_it"+k+" missing"+
(base.equals("corr_td") ? "" : " (rung-2 outputs optional)"));
}
continue;
}
final float [] r = results.getFloats("corr_td_it"+k);
final float [] e = expected.getFloats("expected_corr_td_it"+k);
final float [] r = results.getFloats(base+"_it"+k);
final float [] e = expected.getFloats("expected_"+base+"_it"+k);
double md = 0.0;
int nan_mismatch = 0;
final int n = Math.min(r.length, e.length);
......@@ -434,11 +571,12 @@ public class PoseCorrExport {
}
if ((r.length != e.length) || (nan_mismatch > 0)) md = Double.POSITIVE_INFINITY;
if (debugLevel > -3) {
System.out.println("PoseCorrExport.compareResults: corr_td_it"+k+" max|diff|="+md+
System.out.println("PoseCorrExport.compareResults: "+base+"_it"+k+" max|diff|="+md+
", NaN-mismatch="+nan_mismatch);
}
max_diff = Math.max(max_diff, md);
}
}
return max_diff;
}
}
......@@ -33,6 +33,8 @@ public class CuasRtParameters {
public int pose_cycles = 3; // lean v2 (Andrey's ruling 07/12/2026): run EXACTLY this many LMA measure<->solve cycles per scene, no convergence-based early exit - CUAS scenes are near-identical perturbations of the same view, so the needed count is a constant of the setup (and a fixed count gives the GPU pipeline a static launch shape). The convergence delta is still computed: a QC line flags scenes whose last ATR change misses the legacy imp.exit_change_atr threshold (diagnostic only). 0 = legacy convergence exit (imp.max_cycles cap + exit_change thresholds - for varying-scene/flying-camera applications). // By Claude on 07/12/2026
public double pose_lambda = 0.001; // lean v2 (Andrey 07/12/2026): INITIAL LMA lambda for the inner pose solve, replacing ilp.ilma_lambda (0.1) which restarts every outer measure<->solve cycle. 0.1 + the 0.1%-improvement inner exit = under-relaxation: the inner loop quits while still lambda-limited, the outer cycle re-measures and lambda resets - hence the observed ~0.8/cycle outer decay needing 6-13 cycles. The 3-angle/150-tile problem is heavily overdetermined and near-linear: a near-zero lambda makes the first inner steps ~Gauss-Newton so each outer cycle closes the measurable gap. 0 = legacy (use ilp.ilma_lambda). // By Claude on 07/12/2026
public int pose_max_lma = 1; // lean pose: INNER LMA iteration cap, replacing the shared imp.max_LMA (which stays at ~25 for flying-camera/varying-scene applications). VALIDATED 07/13: with the near-GN start (pose_lambda) the first inner step reaches the model optimum - step 2 only confirms <0.1% - so 1 halves the solver work and makes the shape fully static (pose_cycles x 1); dstored RMS identical to unlimited (0.0350/0.0377/0.0213). 0 = legacy (use imp.max_LMA). // By Claude on 07/13/2026, Andrey's design
public int pose_recenter = 0; // rung-2 frozen GPU contract (Andrey's ruling 07/13/2026): recentering passes for the lean peak measurement - the 15-arg Correlation2d.getMaxXYCmEig cosine-mask refinement (window over the eigen-ellipse, imp.eig_scale_axes*sqrt(lambda)+imp.eig_inc_axes half-axes, re-centroid each pass). 0 = single-pass (today's validated baseline behavior); imp.eig_recenter (2) = the oracle engine's setting - candidate for wide lean peaks, needs its own A/B run. The GPU argmax/centroid/eigen kernel implements the SAME superset contract with this as a runtime parameter. // By Claude on 07/13/2026
public double pose_debias = 0.0; // rung-2 optional envelope de-bias hook (Andrey's ruling 07/13/2026, default OFF): fraction of the MCLT window-autocorr envelope divided out of the pixel-domain correlation BEFORE peak extraction (corr *= envelope^-frac, envelope normalized 1.0 at center). The envelope multiplies the true correlation peak and pulls the centroid toward center - argmax under-reports offsets by alpha ~0.75-0.9 (probed 07/13, the residual per-cycle convergence tax). 0 = off (bit-identical to no hook); 1 = full divide. GPU kernel contract: optional precomputed de-bias array pointer, NULL = off. Enabling needs its own A/B run. // By Claude on 07/13/2026
public int pose_lma_debug = 0; // inner-LMA debug level, passed to the inner runLma (max with the run's own debugLevel): 0 = quiet (default; ALSO gates the per-scene "CuasPoseRT cycles:" trace line - >=1 to print it); 2 = one line PER INNER STEP ("LMA step N: {good,done} full RMS= ... pure RMS= ... + lambda=...") - the convergence trajectory INSIDE each outer cycle; 3-4 = progressively deeper solver dumps (parameter vectors, matrices). VERBOSE at >=2 - use for short investigation runs, not production. // By Claude on 07/12/2026, trace gate 07/13
public boolean rend_test = false; // RT full-render product (CuasRender.testRenderSequence): per scene raw /jp4/ -> conditionSceneToGpu -> virtual-grid render at BORROWED stored pose+ERS rates -> -CUAS-RT-RENDER hyperstack [s00..s15, merged][scenes], comparable to -CUAS-INDIVIDUAL/MERGED-CUAS-DBG. NON-exclusive: runs AFTER the pose stage when both are ON (the pose stage is part of the RT cycle - Andrey 07/06/2026). // By Claude on 07/05/2026
public boolean log_test = false; // LoG A/B with the RT render (CuasRender): also save -CUAS-RT-RENDER-LOG-ORACLE (product convolved with the EXISTING pre-DNN pixel LoG, LINEAR alpha=1) and -CUAS-RT-RENDER-LOG-FOLDED (same render, LoG folded into the GPU aberration kernels via CuasLogFold - no pixel convolution; originals restored). The aberration+LoG kernel-fold validation (RT-seed step 1b). // By Claude on 07/06/2026
......@@ -148,6 +150,10 @@ public class CuasRtParameters {
"Initial lambda of the INNER pose LMA (restarts every outer cycle). The legacy 0.1 start + 0.1%-improvement inner exit under-relaxes the outer loop (~0.8/cycle decay, 6-13 cycles). Near-zero = ~Gauss-Newton first steps for the near-linear 3-angle problem. 0 = legacy ilp.ilma_lambda.");
gd.addNumericField("Pose lean max inner LMA iterations", this.pose_max_lma, 0,3,"", // By Claude on 07/13/2026
"Inner LMA cap for the LEAN pose only (imp 'Max LMA iterations' stays for other applications). 1 = validated CUAS static shape (near-GN first step reaches the optimum; the second step only confirms). 0 = legacy imp.max_LMA.");
gd.addNumericField("Pose peak recentering passes", this.pose_recenter, 0,3,"", // By Claude on 07/13/2026
"Rung-2 frozen GPU peak contract: cosine-mask recentering passes over the eigen-ellipse (the 15-arg getMaxXYCmEig refinement; helps wide lean peaks). 0 = single-pass (the validated baseline); 2 = the oracle engine's imp 'Number of recentering passes'. A/B by pose quality before enabling.");
gd.addNumericField("Pose peak envelope de-bias fraction", this.pose_debias, 5,7,"", // By Claude on 07/13/2026
"Fraction of the MCLT window-autocorr envelope divided out of the correlation before peak extraction (argmax under-reports offsets by ~0.75-0.9x through the envelope). 0 = off (default, bit-identical); 1 = full divide. Enabling needs its own A/B run.");
gd.addNumericField("Pose lean inner-LMA debug level", this.pose_lma_debug, 0,3,"", // By Claude on 07/12/2026
"0 = quiet (also hides the per-scene 'CuasPoseRT cycles:' trace; >=1 shows it); 2 = one line per INNER LMA step (good/done flags, full/pure RMS, lambda); 3-4 = deeper solver dumps. VERBOSE at >=2 - for short investigation runs.");
gd.addCheckbox ("CUAS RT render (full product)", this.rend_test, // By Claude on 07/05/2026
......@@ -333,6 +339,8 @@ public class CuasRtParameters {
this.pose_cycles = (int) gd.getNextNumber(); // By Claude on 07/12/2026
this.pose_lambda = gd.getNextNumber(); // By Claude on 07/12/2026
this.pose_max_lma = (int) gd.getNextNumber(); // By Claude on 07/13/2026
this.pose_recenter = (int) gd.getNextNumber(); // By Claude on 07/13/2026
this.pose_debias = gd.getNextNumber(); // By Claude on 07/13/2026
this.pose_lma_debug =(int) gd.getNextNumber(); // By Claude on 07/12/2026
this.rend_test = gd.getNextBoolean(); // By Claude on 07/05/2026
this.log_test = gd.getNextBoolean(); // By Claude on 07/06/2026
......@@ -437,6 +445,8 @@ public class CuasRtParameters {
properties.setProperty(prefix+"pose_cycles", this.pose_cycles+""); // int // By Claude on 07/12/2026
properties.setProperty(prefix+"pose_lambda", this.pose_lambda+""); // double // By Claude on 07/12/2026
properties.setProperty(prefix+"pose_max_lma", this.pose_max_lma+""); // int // By Claude on 07/13/2026
properties.setProperty(prefix+"pose_recenter", this.pose_recenter+""); // int // By Claude on 07/13/2026
properties.setProperty(prefix+"pose_debias", this.pose_debias+""); // double // By Claude on 07/13/2026
properties.setProperty(prefix+"pose_lma_debug", this.pose_lma_debug+""); // int // By Claude on 07/12/2026
properties.setProperty(prefix+"rend_test", this.rend_test+""); // boolean // By Claude on 07/05/2026
properties.setProperty(prefix+"log_test", this.log_test+""); // boolean // By Claude on 07/06/2026
......@@ -541,6 +551,8 @@ public class CuasRtParameters {
if (properties.getProperty(prefix+"pose_cycles")!=null) this.pose_cycles=Integer.parseInt(properties.getProperty(prefix+"pose_cycles")); // By Claude on 07/12/2026
if (properties.getProperty(prefix+"pose_lambda")!=null) this.pose_lambda=Double.parseDouble(properties.getProperty(prefix+"pose_lambda")); // By Claude on 07/12/2026
if (properties.getProperty(prefix+"pose_max_lma")!=null) this.pose_max_lma=Integer.parseInt(properties.getProperty(prefix+"pose_max_lma")); // By Claude on 07/13/2026
if (properties.getProperty(prefix+"pose_recenter")!=null) this.pose_recenter=Integer.parseInt(properties.getProperty(prefix+"pose_recenter")); // By Claude on 07/13/2026
if (properties.getProperty(prefix+"pose_debias")!=null) this.pose_debias=Double.parseDouble(properties.getProperty(prefix+"pose_debias")); // By Claude on 07/13/2026
if (properties.getProperty(prefix+"pose_lma_debug")!=null) this.pose_lma_debug=Integer.parseInt(properties.getProperty(prefix+"pose_lma_debug")); // By Claude on 07/12/2026
if (properties.getProperty(prefix+"rend_test")!=null) this.rend_test=Boolean.parseBoolean(properties.getProperty(prefix+"rend_test")); // By Claude on 07/05/2026
if (properties.getProperty(prefix+"log_test")!=null) this.log_test=Boolean.parseBoolean(properties.getProperty(prefix+"log_test")); // By Claude on 07/06/2026
......@@ -648,6 +660,8 @@ public class CuasRtParameters {
cp.pose_cycles = this.pose_cycles; // By Claude on 07/12/2026
cp.pose_lambda = this.pose_lambda; // By Claude on 07/12/2026
cp.pose_max_lma = this.pose_max_lma; // By Claude on 07/13/2026
cp.pose_recenter = this.pose_recenter; // By Claude on 07/13/2026
cp.pose_debias = this.pose_debias; // By Claude on 07/13/2026
cp.pose_lma_debug = this.pose_lma_debug; // By Claude on 07/12/2026
cp.rend_test = this.rend_test; // By Claude on 07/05/2026
cp.log_test = this.log_test; // By Claude on 07/06/2026
......
......@@ -465,6 +465,66 @@ public class TDCorrTile {
return mapped_corrs;
}
/**
* IN-PLACE variant of getFromGpu + convertTDtoPD for the interscene (lean pose) path -
* rung 2 first half (roadmap 3-C, Andrey's ruling 07/13/2026): normalize the correlation
* TD buffer WHERE execCorr2D_inter_TD left it instead of the TD D2H -> host repack ->
* H2D -> normalize -> PD D2H round trip. corr2D_normalize is per-tile independent and
* stride-based, so normalizing ALL resident slots (per-sensor + sum; the lean mask-1 run
* has 2 per tile) yields sum-slot PD bit-identical to the repacked path: the repack
* passed per-tile weight = num-pairs-1 = 1.0 (TDCorrTile.getFromGpu weight arg), and
* fat_zero2/1.0f == fat_zero2 == the null-weights path (the JNA backend ignored weights
* already). The raw TD buffer is NOT modified (normalize reads TD, writes the PD buffer),
* so the pose_corr checkpoint-1 capture is unaffected. Only the small index array and
* the PD tiles cross D2H - the TD never leaves the GPU; the PD D2H itself goes away when
* the rung-2 peak kernel lands (only per-tile {dx,dy,str,eigen} will cross).
* NaN-poisoned tiles (consolidation OOB) map to null, as getFromGpu's skip did.
* By Claude on 07/13/2026.
* @param gpuQuad GPU quad instance (after execCorr2D_inter_TD)
* @param gpu_fat_zero absolute fat zero (curt.fz_inter)
* @param debug_level debug level
* @return sparse array in line-scan order, each element null or double[225]
*/
public static double [][] convertTDtoPDInPlace(
final GpuQuad gpuQuad,
final double gpu_fat_zero,
final int debug_level
){
final int gpu_corr_rad = GPUTileProcessor.DTT_SIZE - 1;
final int tilesX = gpuQuad.getImageWidth() / GpuQuad.getDttSize();
final int tilesY = gpuQuad.getImageHeight() / GpuQuad.getDttSize();
final int [] indices = gpuQuad.getCorrIndices(); // small D2H; also refreshes num_corr_tiles for normalize/getCorr2D
gpuQuad.execCorr2D_normalize(
false, // per-pair buffer (the inter output)
gpu_fat_zero,
null, // weights: null == the repacked path's uniform 1.0 (see header)
gpu_corr_rad);
final float [][] fcorr2D = gpuQuad.getCorr2D(gpu_corr_rad);
final double [][] mapped_corrs = new double [tilesX * tilesY][];
final Thread[] threads = ImageDtt.newThreadArray(ImageDtt.THREADS_MAX);
final AtomicInteger ai = new AtomicInteger(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int i = ai.getAndIncrement(); i < indices.length; i = ai.getAndIncrement()) {
if ((indices[i] & 0xff) != 0xff) continue; // sum slot only (correlate2D_inter marks it 0xff)
final float [] ftile = fcorr2D[i];
final double [] dtile = new double [ftile.length];
copy_tile: {
for (int k = 0; k < ftile.length; k++) {
if (Float.isNaN(ftile[k])) break copy_tile; // poisoned (OOB) tile -> null
dtile[k] = ftile[k];
}
mapped_corrs[indices[i] >> GPUTileProcessor.CORR_NTILE_SHIFT] = dtile;
}
}
}
};
}
ImageDtt.startAndJoin(threads);
return mapped_corrs;
}
/**
* Calculate per-tile motion vectors {x,y,strength_over_min) using centroid approach
* @param tiles sparse array of correlation tiles (now 15*15=255 pix)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment