/**
 **
 ** IntersceneLma - Adjust camera egomotion with LMA
 **
 ** Copyright (C) 2020-2023 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  IntersceneLma.java is free software: you can redistribute it and/or modify
 **  it under the terms of the GNU General Public License as published by
 **  the Free Software Foundation, either version 3 of the License, or
 **  (at your option) any later version.
 **
 **  This program is distributed in the hope that it will be useful,
 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 **  GNU General Public License for more details.
 **
 **  You should have received a copy of the GNU General Public License
 **  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ** -----------------------------------------------------------------------------**
 **
 */

package com.elphel.imagej.tileprocessor;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.DoubleAdder;

import javax.xml.bind.DatatypeConverter;

import com.elphel.imagej.common.ShowDoubleFloatArrays;

import Jama.Matrix;
import ij.ImagePlus;

public class IntersceneLma {
	QuadCLT [] scenesCLT =    null; // now will use just 2 - 0 -reference scene, 1 - scene.  
	private double []         last_rms =        null; // {rms, rms_pure}, matching this.vector
	private double []         good_or_bad_rms = null; // just for diagnostics, to read last (failed) rms
	private double []         initial_rms =     null; // {rms, rms_pure}, first-calcualted rms
	private double []         y_vector =        null; // sum of fx(initial parameters) and correlation offsets
	private double []         s_vector =        null; // strength component - just for debug images
	private double []         last_ymfx =       null;
	private double [][]       last_jt =         null;
	private double []         weights; // normalized so sum is 1.0 for all - samples and extra regularization
	private double            sum_weights =     0;
	private int               num_defined =     0;    
	private double            pure_weight; // weight of samples only
	private boolean []        par_mask =        null;
	private int []            par_indices =     null;
	private double  []        parameters_full = null; // full parameters vector for the currenl LMA run
	private double  []        backup_parameters_full = null; // full parameters vector before first LMA run
	private double  []        parameters_vector = null; 
	private double  []        parameters_pull = null; // for regularization - error is proportional to difference between
	//                                                   current vector and parameters_pull
	private double  [][]      macrotile_centers = null;  // (will be used to pull for regularization)
	private double            infinity_disparity = 0.01;  // treat lower as infinity
	private int               num_samples = 0;
	///////////////////////////////////////////////////////////
	// thread_invariant is needed for LMA, otherwise even with the same parameter vector RMS may be slightly different
	// keeping thread-variant where it is done once per LMA (during setup) - in normalizeWeights() and setSamplesWeights()
	// See original code (with all switching between variant/invariant) in pre 12/17/2023
	///////////////////////////////////////////////////////////
	
	private boolean           thread_invariant = true; // Do not use DoubleAdder, provide results not dependent on threads
	private int               num_components =   2;    // 2 for 2D matching only,3 - include disparity
	private double            disparity_weight = 1.0;  // relative weight of disparity errors
	
	private double [][][]     eig_trans = null;
	private int               tilesX = -1;
	private String            dbg_prefix = null;
	/** Optional backend for the two normal-equation products in lmaStep().
	 *  Inputs retain the Java/oracle representation; output is row-major H then b. */
	@FunctionalInterface
	public interface NormalEquationProvider {
		double [] products(double [] weights, double [][] jt, double [] ymfxWeighted);
	}
	private NormalEquationProvider normalEquationProvider = null;
	/** Optional primitive-float backend for the expensive per-tile pose projection/Jacobian. */
	@FunctionalInterface
	public interface PoseFxProvider {
		IntersceneLmaFloat.Result calculate(
				IntersceneLmaFloat.Camera reference,
				IntersceneLmaFloat.Camera scene,
				float [] referenceXyz,
				float [] referenceAtr,
				float [] sceneXyz,
				float [] sceneAtr,
				float [] centers,
				boolean [] selection,
				boolean calculateJacobian);
	}
	private PoseFxProvider poseFxProvider = null;
	/** Optional one-thread float backend for the frozen three-angle LMA candidate. */
	@FunctionalInterface
	public interface PoseLmaStepProvider {
		IntersceneLmaFloat.LmaStepResult calculate(
				float lambda,
				float [] weights,
				float [] jt,
				float [] ymfxWeighted,
				float [] vector);
	}
	private PoseLmaStepProvider poseLmaStepProvider = null;
	/** Optional continuation from raw fx/J already resident in the pose backend. */
	@FunctionalInterface
	public interface PoseLmaResidentStepProvider {
		IntersceneLmaFloat.ResidentLmaStepResult calculate(
				float lambda,
				float [] weights,
				float [] y,
				float [] eigen,
				float [] vector,
				float pureWeight,
				float rmsDiff,
				int numTiles,
				boolean capturePrepared);
	}
	private PoseLmaResidentStepProvider poseLmaResidentStepProvider = null;
	private static boolean poseLmaStepOracleReported = false;
	private static boolean poseLmaAcceptanceOracleReported = false;
	private static boolean poseLmaAcceptanceMismatchReported = false;
	private IntersceneLmaFloat.Camera poseFloatReference = null;
	private IntersceneLmaFloat.Camera poseFloatScene = null;
	private float [] poseFloatCenters = null;
	private boolean [] poseFloatSelection = null;
	private IntersceneLmaFloat.Result poseFloatLastResult = null;
	
	public IntersceneLma(
			boolean thread_invariant,
			double disparity_weight
			) {
		this.num_components =   (disparity_weight > 0) ? 3 : 2;
		this.disparity_weight = disparity_weight;
		this.thread_invariant=  thread_invariant;
	}
	
	public int getNumComponents() {
		return num_components;
	}
	/** Install a normal-equation product backend. Null restores the CPU implementation. */
	public void setNormalEquationProvider(final NormalEquationProvider provider) {
		this.normalEquationProvider = provider;
	}
	/** Install a raw three-angle float pose fx/J backend. Null restores the Java double path. */
	public void setPoseFxProvider(final PoseFxProvider provider) {
		this.poseFxProvider = provider;
	}
	/** Install the fixed three-angle float candidate backend. Null keeps the Java-double solve. */
	public void setPoseLmaStepProvider(final PoseLmaStepProvider provider) {
		this.poseLmaStepProvider = provider;
	}
	/** Install the resident raw-fx/J continuation backend. */
	public void setPoseLmaResidentStepProvider(final PoseLmaResidentStepProvider provider) {
		this.poseLmaResidentStepProvider = provider;
	}
	private void resetPoseFxInputs() {
		poseFloatReference = null;
		poseFloatScene = null;
		poseFloatCenters = null;
		poseFloatSelection = null;
		poseFloatLastResult = null;
	}
	public double [][]       getLastJT(){
		return last_jt;
	}

	public double [] getLastYminusFxWeightedCopy() {
		return (last_ymfx == null) ? null : last_ymfx.clone();
	}

	public int [] getParameterIndicesCopy() {
		return (par_indices == null) ? null : par_indices.clone();
	}

	public double [] getParametersVectorCopy() {
		return (parameters_vector == null) ? null : parameters_vector.clone();
	}

	public static class NormalEquationData {
		public final double [][] h;
		public final double [] b;
		public final int [] parameterIndices;
		public final double [] parameterVector;
		public final double [] rms;

		public NormalEquationData(
				final double [][] h,
				final double [] b,
				final int [] parameterIndices,
				final double [] parameterVector,
				final double [] rms) {
			this.h = h;
			this.b = b;
			this.parameterIndices = parameterIndices;
			this.parameterVector = parameterVector;
			this.rms = rms;
		}
	}

	/**
	 * Return weighted normal-equation components for the current linearization:
	 * <pre>
	 * H = J^T W J
	 * b = J^T W (y - f(x))
	 * </pre>
	 * Use this for global sparse coupling of multiple local problems.
	 *
	 * @param lambda add lambda*diag(H) when > 0 (same convention as local LMA)
	 * @return normal-equation data or {@code null} when the current linearization is not initialized
	 */
	public NormalEquationData getNormalEquationData(
			final double lambda) {
		if ((last_jt == null) || (last_ymfx == null) || (parameters_vector == null) || (par_indices == null)) {
			return null;
		}
		final double [][] h = getWJtJlambda(
				lambda,
				last_jt);
		final Matrix bMatrix = (new Matrix(last_jt)).times(new Matrix(last_ymfx, last_ymfx.length));
		return new NormalEquationData(
				h,
				bMatrix.getColumnPackedCopy(),
				par_indices.clone(),
				parameters_vector.clone(),
				(last_rms == null) ? null : last_rms.clone());
	}
	
	public double getSumWeights() {
		return sum_weights;
	}
	
	public int getNumDefined() {
		return num_defined;
	}

	
	public double[] getInitialRms() { // rung C1: re-measured RMS at the cycle's start pose // By Claude on 07/17/2026
		return initial_rms;
	}

	public double[] getLastRms() {
		return last_rms;
	}
	public double [] getSceneXYZ(boolean initial) {
		double [] full_vector = initial? backup_parameters_full: getFullVector(parameters_vector);
		return new double[] {
				full_vector[ErsCorrection.DP_DSX],full_vector[ErsCorrection.DP_DSY],full_vector[ErsCorrection.DP_DSZ]};
	}
	public double [] getSceneATR(boolean initial) {
		double [] full_vector = initial? backup_parameters_full: getFullVector(parameters_vector);
		return new double[] {
				full_vector[ErsCorrection.DP_DSAZ],full_vector[ErsCorrection.DP_DSTL],full_vector[ErsCorrection.DP_DSRL]};
	}
	public double [][] getSceneXYZATR(boolean initial) {
		double [] full_vector = initial? backup_parameters_full: getFullVector(parameters_vector);
		return new double[][] {
				{full_vector[ErsCorrection.DP_DSX],full_vector[ErsCorrection.DP_DSY],full_vector[ErsCorrection.DP_DSZ]},
				{full_vector[ErsCorrection.DP_DSAZ],full_vector[ErsCorrection.DP_DSTL],full_vector[ErsCorrection.DP_DSRL]}};
	}
	
	public double [] getReferenceXYZ(boolean initial) {
		double [] full_vector = initial? backup_parameters_full: getFullVector(parameters_vector);
		return new double[] {
				full_vector[ErsCorrection.DP_DX],full_vector[ErsCorrection.DP_DY],full_vector[ErsCorrection.DP_DZ]};
	}
	public double [] getReferenceATR(boolean initial) {
		double [] full_vector = initial? backup_parameters_full: getFullVector(parameters_vector);
		return new double[] {
				full_vector[ErsCorrection.DP_DAZ],full_vector[ErsCorrection.DP_DTL],full_vector[ErsCorrection.DP_DRL]};
	}
	public double [][] getReferenceXYZATR(boolean initial) {
		double [] full_vector = initial? backup_parameters_full: getFullVector(parameters_vector);
		return new double[][] {
				{full_vector[ErsCorrection.DP_DX],full_vector[ErsCorrection.DP_DY],full_vector[ErsCorrection.DP_DZ]},
				{full_vector[ErsCorrection.DP_DAZ],full_vector[ErsCorrection.DP_DTL],full_vector[ErsCorrection.DP_DRL]}};
	}

	public double [] getSceneERSXYZ(boolean initial) { // never used
		double [] full_vector = initial? backup_parameters_full: getFullVector(parameters_vector);
		return new double[] {
				full_vector[ErsCorrection.DP_DSVX],full_vector[ErsCorrection.DP_DSVY],full_vector[ErsCorrection.DP_DSVZ]};
	}
	public double [] getSceneERSATR(boolean initial) { // never used
		double [] full_vector = initial? backup_parameters_full: getFullVector(parameters_vector);
		return new double[] {
				full_vector[ErsCorrection.DP_DSVAZ],full_vector[ErsCorrection.DP_DSVTL],full_vector[ErsCorrection.DP_DSVRL]};
	}
	public double [] getReferenceERSXYZ(boolean initial) { // never used
		double [] full_vector = initial? backup_parameters_full: getFullVector(parameters_vector);
		return new double[] {
				full_vector[ErsCorrection.DP_DVX],full_vector[ErsCorrection.DP_DVY],full_vector[ErsCorrection.DP_DVZ]};
	}
	public double [] getReferenceERSATR(boolean initial) { // never used
		double [] full_vector = initial? backup_parameters_full: getFullVector(parameters_vector);
		return new double[] {
				full_vector[ErsCorrection.DP_DVAZ],full_vector[ErsCorrection.DP_DVTL],full_vector[ErsCorrection.DP_DVRL]};
	}

	public String [] printOldNew(boolean allvectors) {
		return printOldNew(allvectors, 0);
	}

	public String [] printOldNew(boolean allvectors, int mode) {
		return printOldNew(allvectors, mode, 9, 6);
	}
	
	public String [] printOldNew(boolean allvectors, int w, int d) {
		return printOldNew(allvectors, 0, w, d);
	}

	public String [] printOldNew(boolean allvectors, int mode, int w, int d) {
		// mode: 0 - auto, 1 - force "was", 2 force "pull"
		
		String fmt1 = String.format("%%%d.%df", w+2,d+2); // more for the differences
		ArrayList<String> lines = new ArrayList<String>();
		for (int n = ErsCorrection.DP_DVAZ; n < ErsCorrection.DP_NUM_PARS; n+=3) {
			boolean adj = false;
			for (int i = 0; i <3; i++) adj |= par_mask[n+i];
			if (allvectors || adj) {
				String line = printNameV3(n, false, mode, w,d)+"  (" + getCompareType(mode)+
						" "+printNameV3(n, true, mode, w,d)+")";
				line += ", diff_last="+String.format(fmt1, getV3Diff(n)[0]);
				line += ", diff_first="+String.format(fmt1, getV3Diff(n)[1]);
				lines.add(line);
			}
		}
		return lines.toArray(new String[lines.size()]);
	}
	
	/**
	 * Calculate L2 for 3-vector difference between the adjusted values, this LMA start values
	 * and "backup" parameters - snapshot before the first adjustment (first calculation of 
	 * the motion vectors+LMA iteration  
	 * @param indx offset to the first element of the 3-vector in the full parameters array
	 * @return a pair of L2 differences {diff_with_this_LMA_start, diff_with_first_lma_start}
	 */
	public double [] getV3Diff(int indx) {
		double [] v_new = new double[3], v_backup = new double[3], v_start = new double[3];
		System.arraycopy(getFullVector(parameters_vector), indx, v_new, 0, 3);
		System.arraycopy(backup_parameters_full, indx, v_backup, 0, 3);
		System.arraycopy(parameters_full, indx, v_start, 0, 3);
		double l2_backup = 0;
		double l2_start = 0;
		for (int i = 0; i < 3; i++) {
			l2_backup += (v_new[i]-v_backup[i]) * (v_new[i]-v_backup[i]); 
			l2_start +=  (v_new[i]-v_start[i]) *  (v_new[i]-v_start[i]); 
		}
		return new double [] {Math.sqrt(l2_start), Math.sqrt(l2_backup)};
	}
	
	public String getCompareType() {
		return getCompareType(0);
//		return (parameters_pull != null)? "pull": "was";
	}

	public String getCompareType(int mode) {
		boolean is_pull = (mode == 0) ? (parameters_pull != null) : (mode > 1);  
		return is_pull? "pull": "was ";
	}
	
	public String printNameV3(int indx, boolean initial, int w, int d) {
		return printNameV3(indx, initial, 0, w, d);
	}

	public String printNameV3(int indx, boolean initial, int mode, int w, int d) {
		// mode: 0 - auto, 1 - was, 2 - pull 
		boolean use_pull = (mode == 0) ? (parameters_pull != null) : (mode > 1);  		
		double [] full_vector = initial? 
				(use_pull? getFullVector(parameters_pull) : backup_parameters_full):
			getFullVector(parameters_vector);
		double [] vector = new double[3];
		for (int i = 0; i <3; i++) {
			vector[i] = full_vector[indx + i];
		}
		String name = ErsCorrection.DP_VECTORS_NAMES[indx];
		return printNameV3(name, vector, w, d);
	}
	
	
	public static String printNameV3(String name, double[] vector) {
		return printNameV3(name, vector, 10, 6);
	}
	
	public static String printNameV3(String name, double[] vector, int w, int d) {
		return String.format("%14s: %s", name, printV3(vector, w, d));
	}

	
	public static String printV3(double[] vector) {
		return printV3(vector, 8, 5);
	}
	public static String printV3(double[] vector, int w, int d) {
		String fmt = String.format("[%%%d.%df, %%%d.%df, %%%d.%df]", w,d,w,d,w,d);
		return String.format(fmt, vector[0], vector[1], vector[2]);
	}

	public void prepareLMA(
			final double [][] scene_xyzatr0,     // camera center in world coordinates (or null to use instance)
			final double [][] scene_xyzatr_pull, // if both are not null, specify target values to pull to 
			final double [][] ref_xyzatr,        // 
			// reference atr, xyz are considered 0.0 - not anymore?
			final QuadCLT     scene_QuadClt,
			final QuadCLT     reference_QuadClt,
			final boolean[]   param_select,
			final double []   param_regweights,
			final double      eig_max_sqrt, //  6;    // for sqrt(lambda) - consider infinity (infinite linear feature, a line)
			final double      eig_min_sqrt, //  1;    // for sqrt(lambda) - consider infinity (infinite linear feature, a line)
			//if (eigen != null) normalize by eigenvalues, recalc derivatives to eigenvectors directions
			final double [][] eigen, // [tilesX*tilesY]{lamb0_x,lamb0_y, lamb0, lamb1} eigenvector0[x,y],lam0,lam1
			// now includes optional Disparity as the last element (for num_components==3)
			final double [][] vector_XYSDS,// optical flow X,Y, confidence obtained from the correlate2DIterate()
			final double      min_confidence,// minimal tile confidence to use. Use 0 to disable
			final boolean []  reliable_ref, // additional mask for vector_XYSDS or null
			final double [][] centers,     // macrotile centers (in pixels and average disparities
			final boolean     same_weights,
			boolean           first_run,
			String            dbg_prefix, // null or image name prefix
			final int         debug_level) {
		// Rung C0 sub-stage attribution (design 2026-07-16); samples are emitted only
		// on the LMA path (vector_XYSDS != null) so MB-vector calls do not pollute
		// the LMA-prepare budget. Null sink = zero overhead. By Claude on 07/17/2026.
		final ProfileSink prof_sink = profileSink;
		final long prof_t0 = (prof_sink != null) ? System.nanoTime() : 0L;
		// Rung C2: the threaded setEigenTransform build is DEAD WORK on the resident
		// path (the GPU assemble builds the transform from the resident peaks) - it
		// is deferred to the legacy/capture fall-through below. By Claude on 07/17/2026.
		eig_trans = null;
		tilesX = reference_QuadClt.getTileProcessor().getTilesX(); // just for debug images
		scenesCLT = new QuadCLT [] {reference_QuadClt, scene_QuadClt};
		resetPoseFxInputs();
		par_mask = param_select;
		macrotile_centers = centers;
		num_samples = num_components * centers.length;
		this.dbg_prefix = dbg_prefix;
		s_vector = (this.dbg_prefix != null) ? (new double[centers.length]): null; // original strength
		ErsCorrection ers_ref =   reference_QuadClt.getErsCorrection();
		ErsCorrection ers_scene = scene_QuadClt.getErsCorrection();
		final double []   scene_xyz = (scene_xyzatr0 != null) ? scene_xyzatr0[0] : ers_scene.camera_xyz;
		final double []   scene_atr = (scene_xyzatr0 != null) ? scene_xyzatr0[1] : ers_scene.camera_atr;
		final double []   reference_xyz = (ref_xyzatr != null)? ref_xyzatr[0]:     ers_ref.camera_xyz; // new double[3];
		final double []   reference_atr = (ref_xyzatr != null)? ref_xyzatr[1]:     ers_ref.camera_xyz; // new double[3];
		double [] full_parameters_vector = new double [] {
				0.0,                             0.0,                             0.0,
				ers_ref.ers_watr_center_dt[0],   ers_ref.ers_watr_center_dt[1],   ers_ref.ers_watr_center_dt[2],
				ers_ref.ers_wxyz_center_dt[0],   ers_ref.ers_wxyz_center_dt[1],   ers_ref.ers_wxyz_center_dt[2],
				reference_atr[0],                reference_atr[1],                reference_atr[2],
				reference_xyz[0],                reference_xyz[1],                reference_xyz[2],
				ers_scene.ers_watr_center_dt[0], ers_scene.ers_watr_center_dt[1], ers_scene.ers_watr_center_dt[2],
				ers_scene.ers_wxyz_center_dt[0], ers_scene.ers_wxyz_center_dt[1], ers_scene.ers_wxyz_center_dt[2],
				scene_atr[0],                    scene_atr[1],                    scene_atr[2],
				scene_xyz[0],                    scene_xyz[1],                    scene_xyz[2]};
		parameters_full = full_parameters_vector.clone();
		if ((vector_XYSDS != null) && (first_run || (backup_parameters_full == null))) {
			backup_parameters_full = full_parameters_vector.clone();
		}
		int num_pars = 0;
		for (int i = 0; i < par_mask.length; i++) if (par_mask[i]) num_pars++;
		par_indices = new int [num_pars];
		num_pars = 0;
		for (int i = 0; i < par_mask.length; i++) {
			if (par_mask[i]) par_indices[num_pars++] = i;
		}
		parameters_vector = new double [par_indices.length];
		for (int i = 0; i < par_indices.length; i++) {
			parameters_vector[i] = full_parameters_vector[par_indices[i]];
		}

		if ((scene_xyzatr_pull != null) && (scene_xyzatr_pull[0] != null) && (scene_xyzatr_pull[1] != null)) {
			double [] parameters_pull_full =  parameters_full.clone();
			for (int i = 0; i < 3; i++)	{
				parameters_pull_full[ErsCorrection.DP_DSX +  i] =  scene_xyzatr_pull[0][i]; 
				parameters_pull_full[ErsCorrection.DP_DSAZ +  i] = scene_xyzatr_pull[1][i]; 
				// 			parameters_pull = 
			}
			parameters_pull = new double [par_indices.length];
			for (int i = 0; i < par_indices.length; i++) {
				parameters_pull[i] = parameters_pull_full[par_indices[i]];
			}
		}
		
		// RUNG C1 (design 2026-07-16): resident prepare replaces setSamplesWeights /
		// both fx passes / WJtJ+reg+normalize / y-build - the measured inputs never
		// leave the GPU; initial RMS comes from the first resident step's packed
		// [19,20]. The one-shot oracle cycle (pose_lma_debug>=1, first prepare) runs
		// the FULL legacy path and compares the captured buffers at the end.
		// By Claude on 07/17/2026.
		// Rung C3/C3b: the result of the SECOND full prepare carries the FROZEN
		// conditioning (weights/eigen/selection/pull/reg/pure_weight) - later
		// cycles of the same scene run a LIGHT prepare (fresh measured offsets +
		// fx only). Cycle 1 is never the freeze source (its peaks are measured at
		// the blurriest, pre-correction pose - see C3 gate failure). The instance
		// is per-scene, so this naturally resets per scene. By Claude on 07/16/2026.
		final IntersceneLmaFloat.PrepareResidentResult frozen_prepare =
				((poseFreezeCycles > 0) && (poseResidentFullPrepares >= poseFreezeCycles)) ?
						poseResidentPrepare : null; // 0 = never freeze (runtime-selectable accuracy) // By Claude on 07/16/2026
		poseResidentPrepare = null;
		boolean lean_pose_shape = (num_components == 2) &&
				(par_indices.length == IntersceneLmaFloat.NUM_PARAMS);
		if (lean_pose_shape) {
			for (int par = 0; par < par_indices.length; par++) {
				if (par_indices[par] != ErsCorrection.DP_DSAZ + par) { lean_pose_shape = false; break; }
			}
		}
		final boolean prepare_capture = (poseLmaPrepareProvider != null) && lean_pose_shape &&
				(vector_XYSDS != null) && (debug_level >= 1) && !poseLmaPrepareOracleReported;
		if ((poseLmaPrepareProvider != null) && lean_pose_shape && (vector_XYSDS != null) &&
				(reliable_ref == null) && !prepare_capture) {
			if (residentPrepare(eigen, eig_max_sqrt, eig_min_sqrt, min_confidence,
					same_weights, param_regweights, frozen_prepare, false)) {
				weights = null;  // resident-owned; the steps run with no per-step H2D
				y_vector = null;
				if (prof_sink != null) prof_sink.add(PROF_PREP_SETUP, System.nanoTime() - prof_t0);
				return;
			} // invalid/unavailable -> loud legacy fallback below
			System.out.println("IntersceneLma: resident prepare unavailable - legacy Java prepare");
		}
		// Legacy / capture / MB fall-through: build the eigen transforms here
		// (deferred from the head - dead work on the resident path). By Claude on 07/17/2026.
		eig_trans = setEigenTransform(
				eig_max_sqrt, // final double   eig_max_sqrt,
				eig_min_sqrt, // final double   eig_min_sqrt,
				eigen); // final double [][] eigen); // [tilesX*tilesY]{lamb0_x,lamb0_y, lamb0, lamb1} eigenvector0[x,y],lam0,lam1
		if (vector_XYSDS != null) {  // skip when used for the motion blur vectors, not LMA
			setSamplesWeights(vector_XYSDS,  // not regularized yet ! // 3d updated
					min_confidence,  // final double      min_confidence,// minimal tile confidence to use. Use 0 to disable
					reliable_ref,    // final boolean []  reliable_ref, // additional mask for vector_XYSDS or null
					same_weights);   // final boolean same_weights) // same weight if > 0

		} else {
			weights = null; // new double[2 * centers.length];
		}

		last_jt = new double [parameters_vector.length][];
		if (debug_level > 1) {
			System.out.println("prepareLMA() 1");
		}
		final long prof_t1 = (prof_sink != null) ? System.nanoTime() : 0L; // By Claude on 07/17/2026
		double [] fx = getFxDerivs(
				parameters_vector, // double []         vector,
				last_jt,           // final double [][] jt, // should be null or initialized with [vector.length][]
				scenesCLT[1],      // final QuadCLT     scene_QuadClt,
				scenesCLT[0],      // final QuadCLT     reference_QuadClt,
				debug_level);      // final int         debug_level)
		if (vector_XYSDS == null) {
			return; // for MB vectors (noLMA)
		}
		long prof_t2 = 0L; // rung C0 boundaries // By Claude on 07/17/2026
		if (prof_sink != null) {
			prof_t2 = System.nanoTime();
			prof_sink.add(PROF_PREP_SETUP, prof_t1 - prof_t0);
			prof_sink.add(PROF_PREP_FX1,   prof_t2 - prof_t1);
		}
		double [][] wjtj = getWJtJlambda( // USED in lwir all NAN
				0.0,               // final double      lambda,
				last_jt);               // final double [][] jt) all 0???
		for (int i = 0; i < parameters_vector.length; i++) {
			int indx = num_samples + i;
			if (wjtj[i][i] == 0) {  // why is it zero (leading to NaN)
				weights[indx] = 0;
			} else {
				weights[indx] = param_regweights[par_indices[i]]/Math.sqrt(wjtj[i][i]);
			}
		}
		normalizeWeights(); // make full weight == 1.0; pure_weight <= 1.0;
		long prof_t3 = 0L; // By Claude on 07/17/2026
		if (prof_sink != null) {
			prof_t3 = System.nanoTime();
			prof_sink.add(PROF_PREP_WJTJ, prof_t3 - prof_t2);
		}
		// remeasure fx - now with regularization terms.
		if (debug_level > 1) {
			System.out.println("prepareLMA() 2");
		}
		fx = getFxDerivs(
				parameters_vector, // double []         vector,
				last_jt,                // final double [][] jt, // should be null or initialized with [vector.length][]
				scene_QuadClt,     // final QuadCLT     scene_QuadClt,
				reference_QuadClt, // final QuadCLT     reference_QuadClt,
				debug_level);      // final int         debug_level)
		if (prof_sink != null) { // y-build+RMS tail = derived remainder in the profile // By Claude on 07/17/2026
			prof_sink.add(PROF_PREP_FX2, System.nanoTime() - prof_t3);
		}
		// Why y_vector starts with initial value of fx???
		y_vector = fx.clone();
		for (int i = 0; i < vector_XYSDS.length; i++) {
			if (vector_XYSDS[i] != null){
				y_vector[num_components * i + 0] += vector_XYSDS[i][0];
				y_vector[num_components * i + 1] += vector_XYSDS[i][1];
				if (num_components > 2) { // ****************************** [3] - disparity? Was BUG: [2] !
					y_vector[num_components * i + 2] += vector_XYSDS[i][3]; // vector_XYSDS[i][2]; 
				}
				if (s_vector != null) {
					s_vector[i] = vector_XYSDS[i][2];
				}
			}
		}
			
		if (parameters_pull != null){
			for (int i = 0; i < par_indices.length; i++) {
				y_vector [i + num_components * macrotile_centers.length] = parameters_pull[i]; // - parameters_initial[i]; // scale will be combined with weights
			}			
		}
		
		last_rms = new double [2];
		last_ymfx = getYminusFxWeighted(
				fx, // final double []   fx,
				last_rms); // final double []   rms_fp // null or [2]
		initial_rms = last_rms.clone();
		good_or_bad_rms = this.last_rms.clone();
		// RUNG C1 one-shot oracle (first prepare at pose_lma_debug>=1): the legacy
		// Java prepare above stays authoritative for THIS cycle; the resident
		// prepare runs with capture and the serial float clone must match the
		// captured buffers bit-exactly. By Claude on 07/17/2026.
		if (prepare_capture) {
			if (residentPrepare(eigen, eig_max_sqrt, eig_min_sqrt, min_confidence,
					same_weights, param_regweights, null, true) &&
					(poseResidentPrepare.capturedY != null)) {
				final float [] pull = new float [IntersceneLmaFloat.NUM_PARAMS];
				final float [] regw = new float [IntersceneLmaFloat.NUM_PARAMS];
				for (int par = 0; par < IntersceneLmaFloat.NUM_PARAMS; par++) {
					pull[par] = (float) ((parameters_pull != null) ?
							parameters_pull[par] : parameters_vector[par]);
					regw[par] = (float) param_regweights[par_indices[par]];
				}
				final IntersceneLmaFloat.PrepareResidentOracle oracle =
						IntersceneLmaFloat.prepareResidentOracle(
								vector_XYSDS, eigen, poseFloatLastResult,
								(float) eig_min_sqrt, (float) eig_max_sqrt,
								(float) min_confidence, same_weights, pull, regw);
				System.out.println("IntersceneLma resident CUDA vs Java-float PREPARE: "+
						IntersceneLmaFloat.comparePrepareResident(poseResidentPrepare, oracle));
			} else {
				System.out.println("IntersceneLma: resident prepare capture unavailable - legacy prepare retained");
			}
			poseLmaPrepareOracleReported = true;
			poseResidentPrepare = null; // the capture cycle stays fully legacy
		}
	}
	
	
	
	public void prepareLMA(
			final double []   scene_xyz0,     // camera center in world coordinates (or null to use instance)
			final double []   scene_atr0,     // camera orientation relative to world frame (or null to use instance)
			final double []   scene_xyz_pull, // if both are not null, specify target values to pull to 
			final double []   scene_atr_pull, // 
			// reference atr, xyz are considered 0.0 - not anymore?
			final QuadCLT     scene_QuadClt,
			final QuadCLT     reference_QuadClt,
			final boolean[]   param_select,
			final double []   param_regweights,
//			final double [][] vector_XYS,  // optical flow X,Y, confidence obtained from the correlate2DIterate()
			// now includes optional Disparity as the last element (for num_components==3)
			final double [][] vector_XYSDS,  // optical flow X,Y, confidence obtained from the correlate2DIterate()
			final double      min_confidence,// minimal tile confidence to use. Use 0 to disable
			final double [][] centers,       // macrotile centers (in pixels and average disparities
			final boolean     same_weights,
			boolean           first_run,
			final int         debug_level)
	{
		scenesCLT = new QuadCLT [] {reference_QuadClt, scene_QuadClt};
		resetPoseFxInputs();
		par_mask = param_select;
		macrotile_centers = centers;
		num_samples = num_components * centers.length;
		ErsCorrection ers_ref =   reference_QuadClt.getErsCorrection();
		ErsCorrection ers_scene = scene_QuadClt.getErsCorrection();
		final double []   scene_xyz = (scene_xyz0 != null) ? scene_xyz0 : ers_scene.camera_xyz;
		final double []   scene_atr = (scene_atr0 != null) ? scene_atr0 : ers_scene.camera_atr;
		final double []   reference_xyz = new double[3];
		final double []   reference_atr = new double[3];
		double [] full_parameters_vector = new double [] {
				0.0,                             0.0,                             0.0,
				ers_ref.ers_watr_center_dt[0],   ers_ref.ers_watr_center_dt[1],   ers_ref.ers_watr_center_dt[2],
				ers_ref.ers_wxyz_center_dt[0],   ers_ref.ers_wxyz_center_dt[1],   ers_ref.ers_wxyz_center_dt[2],
				reference_atr[0],                reference_atr[1],                reference_atr[2],
				reference_xyz[0],                reference_xyz[1],                reference_xyz[2],
				ers_scene.ers_watr_center_dt[0], ers_scene.ers_watr_center_dt[1], ers_scene.ers_watr_center_dt[2],
				ers_scene.ers_wxyz_center_dt[0], ers_scene.ers_wxyz_center_dt[1], ers_scene.ers_wxyz_center_dt[2],
				scene_atr[0],                    scene_atr[1],                    scene_atr[2],
				scene_xyz[0],                    scene_xyz[1],                    scene_xyz[2]};
		parameters_full = full_parameters_vector.clone();
		if ((vector_XYSDS != null) && (first_run || (backup_parameters_full == null))) {
			backup_parameters_full = full_parameters_vector.clone();
		}
		int num_pars = 0;
		for (int i = 0; i < par_mask.length; i++) if (par_mask[i]) num_pars++;
		par_indices = new int [num_pars];
		num_pars = 0;
		for (int i = 0; i < par_mask.length; i++) {
			if (par_mask[i]) par_indices[num_pars++] = i;
		}
		parameters_vector = new double [par_indices.length];
		for (int i = 0; i < par_indices.length; i++) {
			parameters_vector[i] = full_parameters_vector[par_indices[i]];
		}

		if ((scene_xyz_pull != null) && (scene_atr_pull != null)) {
			double [] parameters_pull_full =  parameters_full.clone();
			for (int i = 0; i < 3; i++)	{
				parameters_pull_full[ErsCorrection.DP_DSX +  i] =  scene_xyz_pull[i]; 
				parameters_pull_full[ErsCorrection.DP_DSAZ +  i] = scene_atr_pull[i]; 
				// 			parameters_pull = 
			}
			parameters_pull = new double [par_indices.length];
			for (int i = 0; i < par_indices.length; i++) {
				parameters_pull[i] = parameters_pull_full[par_indices[i]];
			}
		}
		
		if (vector_XYSDS != null) {// skip when used for the motion blur vectors, not LMA
			setSamplesWeights(vector_XYSDS,  // not regularized yet ! // 3d updated
					 min_confidence,// final double      min_confidence,// minimal tile confidence to use. Use 0 to disable
					null, // final boolean []  reliable_ref, // additional mask for vector_XYSDS or null
					same_weights); // final boolean same_weights) // same weight if > 0
		} else {
			weights = null; // new double[2 * centers.length];
		}

		last_jt = new double [parameters_vector.length][];
		if (debug_level > 1) {
			System.out.println("prepareLMA() 1");
		}
		double [] fx = getFxDerivs(
				parameters_vector, // double []         vector,
				last_jt,           // final double [][] jt, // should be null or initialized with [vector.length][]
				scenesCLT[1],      // final QuadCLT     scene_QuadClt,
				scenesCLT[0],      // final QuadCLT     reference_QuadClt,
				debug_level);      // final int         debug_level)
		if (vector_XYSDS == null) {
			return; // for MB vectors (noLMA)
		}
		
		double [][] wjtj = getWJtJlambda( // USED in lwir all NAN
				0.0,               // final double      lambda,
				last_jt);               // final double [][] jt) all 0???
		for (int i = 0; i < parameters_vector.length; i++) {
			int indx = num_samples + i;
			if (wjtj[i][i] == 0) {  // why is it zero (leading to NaN)
				weights[indx] = 0;
			} else {
				weights[indx] = param_regweights[par_indices[i]]/Math.sqrt(wjtj[i][i]);
			}
		}
		normalizeWeights(); // make full weight == 1.0; pure_weight <= 1.0;
		// remeasure fx - now with regularization terms.

		if (debug_level > 1) {
			System.out.println("prepareLMA() 2");
		}
		fx = getFxDerivs(
				parameters_vector, // double []         vector,
				last_jt,                // final double [][] jt, // should be null or initialized with [vector.length][]
				scene_QuadClt,     // final QuadCLT     scene_QuadClt,
				reference_QuadClt, // final QuadCLT     reference_QuadClt,
				debug_level);      // final int         debug_level)
		// Why y_vector starts with initial value of fx??? 
		y_vector = fx.clone();
		for (int i = 0; i < vector_XYSDS.length; i++) {
			if (vector_XYSDS[i] != null){
				y_vector[num_components * i + 0] += vector_XYSDS[i][0];
				y_vector[num_components * i + 1] += vector_XYSDS[i][1];
				if (num_components > 2) {
					y_vector[num_components * i + 2] += vector_XYSDS[i][2];
				}
				
			}
		}
		if (parameters_pull != null){
			for (int i = 0; i < par_indices.length; i++) {
//				y_vector [i + num_components * macrotile_centers.length] += parameters_pull[i]; // - parameters_initial[i]; // scale will be combined with weights
				y_vector [i + num_components * macrotile_centers.length] = parameters_pull[i]; // - parameters_initial[i]; // scale will be combined with weights
			}			
		}
		
		last_rms = new double [2];
		last_ymfx = getYminusFxWeighted(
				fx, // final double []   fx,
				last_rms); // final double []   rms_fp // null or [2]
		initial_rms = last_rms.clone();
		good_or_bad_rms = this.last_rms.clone();
	}

	/**
	 * Recalculate local Jacobian/residual at a new linearization point while keeping the previously
	 * prepared {@code y_vector} fixed. This follows the same semantics as iterative LMA steps:
	 * target observations stay constant, only {@code f(x)} and {@code J(x)} are updated.
	 *
	 * @param scene_xyzatr0 current scene pose {{x,y,z},{a,t,r}}
	 * @param ref_xyzatr current reference pose {{x,y,z},{a,t,r}}
	 * @param scene_QuadClt current scene object
	 * @param reference_QuadClt current reference scene object
	 * @param debug_level debug verbosity
	 * @return true if residual vector was successfully recomputed
	 */
	public boolean relinearizeAt(
			final double [][] scene_xyzatr0,
			final double [][] ref_xyzatr,
			final QuadCLT     scene_QuadClt,
			final QuadCLT     reference_QuadClt,
			final int         debug_level) {
		if ((y_vector == null) || (weights == null) || (par_indices == null) || (par_mask == null)) {
			return false;
		}
		scenesCLT = new QuadCLT [] {reference_QuadClt, scene_QuadClt};
		resetPoseFxInputs();
		final ErsCorrection ers_ref =   reference_QuadClt.getErsCorrection();
		final ErsCorrection ers_scene = scene_QuadClt.getErsCorrection();
		final double [] scene_xyz = (scene_xyzatr0 != null) ? scene_xyzatr0[0] : ers_scene.camera_xyz;
		final double [] scene_atr = (scene_xyzatr0 != null) ? scene_xyzatr0[1] : ers_scene.camera_atr;
		final double [] reference_xyz = (ref_xyzatr != null) ? ref_xyzatr[0] : ers_ref.camera_xyz;
		final double [] reference_atr = (ref_xyzatr != null) ? ref_xyzatr[1] : ers_ref.camera_xyz;
		final double [] full_parameters_vector = new double [] {
				0.0,                             0.0,                             0.0,
				ers_ref.ers_watr_center_dt[0],   ers_ref.ers_watr_center_dt[1],   ers_ref.ers_watr_center_dt[2],
				ers_ref.ers_wxyz_center_dt[0],   ers_ref.ers_wxyz_center_dt[1],   ers_ref.ers_wxyz_center_dt[2],
				reference_atr[0],                reference_atr[1],                reference_atr[2],
				reference_xyz[0],                reference_xyz[1],                reference_xyz[2],
				ers_scene.ers_watr_center_dt[0], ers_scene.ers_watr_center_dt[1], ers_scene.ers_watr_center_dt[2],
				ers_scene.ers_wxyz_center_dt[0], ers_scene.ers_wxyz_center_dt[1], ers_scene.ers_wxyz_center_dt[2],
				scene_atr[0],                    scene_atr[1],                    scene_atr[2],
				scene_xyz[0],                    scene_xyz[1],                    scene_xyz[2]};
		parameters_full = full_parameters_vector.clone();
		if ((parameters_vector == null) || (parameters_vector.length != par_indices.length)) {
			parameters_vector = new double [par_indices.length];
		}
		for (int i = 0; i < par_indices.length; i++) {
			parameters_vector[i] = full_parameters_vector[par_indices[i]];
		}
		if ((last_jt == null) || (last_jt.length != parameters_vector.length)) {
			last_jt = new double [parameters_vector.length][];
		}
		final double [] fx = getFxDerivs(
				parameters_vector, // double []         vector,
				last_jt,           // final double [][] jt, // should be null or initialized with [vector.length][]
				scene_QuadClt,     // final QuadCLT     scene_QuadClt,
				reference_QuadClt, // final QuadCLT     reference_QuadClt,
				debug_level);      // final int         debug_level)
		last_rms = new double [2];
		last_ymfx = getYminusFxWeighted(
				fx,       // final double []   fx,
				last_rms);// final double []   rms_fp // null or [2]
		good_or_bad_rms = (last_rms == null) ? null : last_rms.clone();
		return last_ymfx != null;
	}
	
	public int runLma( // <0 - failed, >=0 iteration number (1 - immediately)
			double lambda,           // 0.1
			double lambda_scale_good,// 0.5
			double lambda_scale_bad, // 8.0
			double lambda_max,       // 100
			double rms_diff,         // 0.001
			int    num_iter,         // 20
			boolean last_run,
			int    debug_level)
	{
		boolean [] rslt = {false,false};
		this.last_rms = null; // remove?
		int iter = 0;
		for (iter = 0; iter < num_iter; iter++) {
			rslt =  lmaStep(
					lambda,
					rms_diff,
					debug_level);
			if (rslt == null) {
				return -1; // false; // need to check
			}
			if (debug_level > 1) {
				System.out.println("LMA step "+iter+": {"+rslt[0]+","+rslt[1]+"} full RMS= "+good_or_bad_rms[0]+
						" ("+initial_rms[0]+"), pure RMS="+good_or_bad_rms[1]+" ("+initial_rms[1]+") + lambda="+lambda);
			}
			if (rslt[1]) {
				break;
			}
			if (rslt[0]) { // good
				lambda *= lambda_scale_good;
			} else {
				lambda *= lambda_scale_bad;
				if (lambda > lambda_max) {
					break; // not used in lwir
				}
			}
			if (dbg_prefix != null) {
				showDebugImage(dbg_prefix+"-"+iter+(rslt[0]?"-GOOD":"-BAD"));
			}

		}
		if (rslt[0]) { // better
			if (iter >= num_iter) { // better, but num tries exceeded
				if (debug_level > 1) System.out.println("Step "+iter+": Improved, but number of steps exceeded maximal");
			} else {
				if (debug_level > 1) System.out.println("Step "+iter+": LMA: Success");
			}

		} else { // improved over initial ?
			if (last_rms[0] < initial_rms[0]) { // NaN
				rslt[0] = true;
				if (debug_level > 1) System.out.println("Step "+iter+": Failed to converge, but result improved over initial");
			} else {
				if (debug_level > 1) System.out.println("Step "+iter+": Failed to converge");
			}
		}
		if (dbg_prefix != null) {
			showDebugImage(dbg_prefix+"-FINAL");
		}
		boolean show_intermediate = true;
		if (show_intermediate && (debug_level > 0)) {
			System.out.println("LMA: full RMS="+last_rms[0]+" ("+initial_rms[0]+"), pure RMS="+last_rms[1]+" ("+initial_rms[1]+") + lambda="+lambda);
		}
		if (debug_level > 2){ 
			String [] lines1 = printOldNew(false); // boolean allvectors)
			System.out.println("iteration="+iter);
			for (String line : lines1) {
				System.out.println(line);
			}
		}
		if (debug_level > 0) {
			if ((debug_level > 1) ||  last_run) { // (iter == 1) || last_run) {
				if (!show_intermediate) {
					System.out.println("LMA: iter="+iter+",   full RMS="+last_rms[0]+" ("+initial_rms[0]+"), pure RMS="+last_rms[1]+" ("+initial_rms[1]+") + lambda="+lambda);
				}
				String [] lines = printOldNew(false); // boolean allvectors)
				for (String line : lines) {
					System.out.println(line);
				}
			}
		}
		if ((debug_level > -2) && !rslt[0]) { // failed
			if ((debug_level > 1) || (iter == 1) || last_run) {
				System.out.println("LMA failed on iteration = "+iter);
				String [] lines = printOldNew(true); // boolean allvectors)
				for (String line : lines) {
					System.out.println(line);
				}
			}
			System.out.println();
		}

		return rslt[0]? iter : -1;
	}
	
	private boolean [] lmaStep(
			double lambda,
			double rms_diff,
			int debug_level) {
		boolean [] rslt = {false,false};
		// maybe the following if() branch is not needed - already done in prepareLMA !
		// Rung C1: with the resident prepare live there is nothing to linearize in
		// Java (weights/y are GPU-resident); initial RMS seeds from the first
		// resident step's packed[19,20]. By Claude on 07/17/2026.
		if ((this.last_rms == null) && (poseResidentPrepare == null)) { //first time, need to calculate all (vector is valid)
			last_rms = new double[2];
			if (debug_level > 1) {
				System.out.println("lmaStep(): first step");
			}
			double [] fx = getFxDerivs(
					parameters_vector, // double []         vector,
					last_jt,           // final double [][] jt, // should be null or initialized with [vector.length][]
					scenesCLT[1],      // final QuadCLT     scene_QuadClt,
					scenesCLT[0],      // final QuadCLT     reference_QuadClt,
					debug_level);      // final int         debug_level)
			last_ymfx = getYminusFxWeighted(
					fx, // final double []   fx,
					last_rms); // final double []   rms_fp // null or [2]
			this.initial_rms = this.last_rms.clone();
			this.good_or_bad_rms = this.last_rms.clone();
			if (dbg_prefix != null) { // -1) { // temporary
				String      dbg_title= scenesCLT[0].getImageName()+"-"+scenesCLT[1].getImageName()+"-FxDerivs";
				int         dbg_num_rows = 3;
				boolean     dbg_show_reg = true;
				boolean     dbg_show =     true;
				showFxDerivs(
						fx,           // double []   fX,
						last_jt,      // double [][] jt,
						0,            // int         num_rows, // <=0 - stack 3?
						dbg_show_reg, // boolean     show_reg,  // extra row for regularization parameters
						dbg_show,     // boolean     show,
						dbg_title);   // String      title);
				showFxDerivs(
						fx,           // double []   fX,
						last_jt,      // double [][] jt,
						dbg_num_rows, // int         num_rows, // <=0 - stack 3?
						dbg_show_reg, // boolean     show_reg,  // extra row for regularization parameters
						dbg_show, // boolean     show,
						dbg_title); // String      title);
				
				double      debug_delta = 1e-4;
				boolean     show_img = true;
/*				debugDerivs(
						parameters_vector, // double []         vector,
						scenesCLT[1],      // final QuadCLT     scene_QuadClt,
						scenesCLT[0],      // final QuadCLT     reference_QuadClt,
						debug_delta,       // final double      delta,
						show_img,          // final boolean     show_img,
						debug_level);      // final int         debugLevel) */
				/*
				dbgYminusFxWeight(
						this.last_ymfx,
						this.weights,
						"Initial_y-fX_after_moving_objects");
                */
			}
			if (last_ymfx == null) {
				return null; // need to re-init/restart LMA
			}
			// TODO: Restore/implement
			if (debug_level > 3) {
				/*
				 dbgJacobians(
							corr_vector, // GeometryCorrection.CorrVector corr_vector,
							1E-5, // double delta,
							true); //boolean graphic)
				*/
			}
			if (dbg_prefix != null) {
				showDebugImage(dbg_prefix+"-INIT");
			}
		}
		final boolean poseStepShape = (poseResidentPrepare != null) || isPoseLmaStepShape();
		// Rung C1: prepared-resident steps never capture (the capture cycle runs
		// fully legacy, so the step oracle has its Java arrays). By Claude on 07/17/2026.
		final boolean preparedResident = (poseResidentPrepare != null);
		final boolean captureResidentOracle = (debug_level >= 1) &&
				!poseLmaStepOracleReported && !preparedResident;
		IntersceneLmaFloat.LmaStepResult residentPoseStep = null;
		IntersceneLmaFloat.ResidentLmaStepResult residentResult = null;
		if ((poseLmaResidentStepProvider != null) && poseStepShape &&
				(preparedResident ||
				 ((poseFloatLastResult != null) && (poseFloatLastResult.jt != null)))) {
			final int stepNumTiles = preparedResident ?
					poseResidentPrepare.numTiles : poseFloatLastResult.numTiles;
			final float [] floatWeights = preparedResident ? null : toFloatArray(weights);
			final float [] floatVector = toFloatArray(parameters_vector);
			final float [] floatY = preparedResident ? null : toFloatArray(y_vector);
			final float [] floatEigen = preparedResident ? null : flattenEigenTransform(
					eig_trans, stepNumTiles);
			residentResult = poseLmaResidentStepProvider.calculate(
					(float) lambda, floatWeights, floatY, floatEigen, floatVector,
					(float) pure_weight, (float) rms_diff, stepNumTiles,
					captureResidentOracle);
			if (residentResult != null) residentPoseStep = residentResult.step;
			if (captureResidentOracle && (residentPoseStep != null)) {
				if (residentResult.prepared == null) {
					throw new IllegalStateException("IntersceneLma: resident LMA oracle arrays missing");
				}
				final IntersceneLmaFloat.PreparedLma javaPrepared =
						IntersceneLmaFloat.prepareLma(
								poseFloatLastResult, floatY, floatWeights, floatEigen,
								floatVector, (float) pure_weight);
				final IntersceneLmaFloat.PreparationComparison preparationComparison =
						IntersceneLmaFloat.comparePreparation(residentResult.prepared, javaPrepared);
				System.out.println("IntersceneLma resident CUDA vs Java-float preparation: "+
						preparationComparison.format());
				final IntersceneLmaFloat.LmaStepResult javaFloatStep = IntersceneLmaFloat.lmaStep(
						(float) lambda, floatWeights, javaPrepared.jt,
						javaPrepared.ymfxWeighted, floatVector);
				final IntersceneLmaFloat.LmaStepComparison comparison =
						IntersceneLmaFloat.compareLmaSteps(residentPoseStep, javaFloatStep);
				System.out.println("IntersceneLma resident hierarchical vs Java-float serial candidate: "+
						comparison.format());
			}
		}

		final boolean residentCandidateValid = (residentPoseStep != null) &&
				residentPoseStep.valid &&
				(residentPoseStep.candidate.length == parameters_vector.length);
		// RUNG B (design 2026-07-16): with the resident machinery active, an invalid
		// GPU result (singular solve / non-finite) is a REJECTED STEP - the caller
		// raises lambda and retries or exits, exactly the LMA semantics of a singular
		// H+lambda*diag. No Java-double fallback for the lean shape (fallbacks only
		// where free); general shapes never enter this branch. By Claude on 07/16/2026.
		final boolean residentMachineryActive = (poseLmaResidentStepProvider != null) &&
				poseStepShape && (preparedResident ||
						((poseFloatLastResult != null) && (poseFloatLastResult.jt != null)));
		if (residentMachineryActive && !residentCandidateValid) {
			System.out.println("IntersceneLma: resident pose LMA step invalid "+
					"(singular solve / non-finite) - rejected-step semantics, lambda="+lambda);
			return rslt; // {false, false}: not improved, caller decides
		}
		DoubleLmaStepResult doubleStep = null;
		if (!residentCandidateValid || captureResidentOracle) {
			doubleStep = calculateDoubleLmaStep(lambda, debug_level);
			if ((doubleStep == null) && !residentCandidateValid) {
				rslt[1] = true;
				return rslt;
			}
		}

		double [] new_vector;
		if (residentCandidateValid) {
			new_vector = new double [residentPoseStep.candidate.length];
			for (int i = 0; i < new_vector.length; i++) {
				new_vector[i] = residentPoseStep.candidate[i];
			}
		} else {
			new_vector = doubleStep.candidate.clone();
		}

		if (captureResidentOracle && (residentPoseStep != null)) {
			if (doubleStep == null) {
				System.out.println("IntersceneLma resident CUDA-float: Java-double oracle is singular; "+
						"resident candidate retained");
			} else {
				reportPoseStepVsDouble("resident CUDA-float", residentPoseStep,
						doubleStep.products, doubleStep.delta, doubleStep.candidate);
			}
			poseLmaStepOracleReported = true;
		}

		if (!residentCandidateValid && (poseLmaStepProvider != null) && poseStepShape) {
			final float [] floatWeights = toFloatArray(weights);
			final float [] floatVector = toFloatArray(parameters_vector);
			IntersceneLmaFloat.LmaStepResult poseStep;
			final float [] floatJt = flattenJtFloat(last_jt);
			final float [] floatYmfx = toFloatArray(last_ymfx);
			poseStep = poseLmaStepProvider.calculate(
					(float) lambda, floatWeights, floatJt, floatYmfx, floatVector);
			if ((debug_level >= 1) && !poseLmaStepOracleReported && (poseStep != null)) {
				final IntersceneLmaFloat.LmaStepResult javaFloatStep = IntersceneLmaFloat.lmaStep(
						(float) lambda, floatWeights, floatJt, floatYmfx, floatVector);
				final IntersceneLmaFloat.LmaStepComparison comparison =
						IntersceneLmaFloat.compareLmaSteps(poseStep, javaFloatStep);
				System.out.println("IntersceneLma CUDA vs Java-float runLma candidate: "+
						comparison.format());
				reportPoseStepVsDouble("CUDA-float", poseStep,
						doubleStep.products, doubleStep.delta, doubleStep.candidate);
				poseLmaStepOracleReported = true;
			}
			if ((poseStep != null) && poseStep.valid &&
					(poseStep.candidate.length == new_vector.length)) {
				for (int i = 0; i < new_vector.length; i++) new_vector[i] = poseStep.candidate[i];
			}
		}
		
		// RUNG B (design 2026-07-16): the resident GPU decision is AUTHORITATIVE for
		// the lean shape. Accept/stop/RMS come from packed[19..24]; the production
		// path projects NOTHING in Java - no candidate getFxDerivs, no re-decision,
		// no rejection-restore projection (the GPU retained the current raw state by
		// contract, device-side commit). The pre-existing decision equivalence was
		// proven over 1,988 steps with zero mismatches (session-43 gate). The one-shot
		// oracle (captureResidentOracle, pose_lma_debug>=1) falls through to the
		// legacy path below ONCE per run to print the Java-double comparison.
		// By Claude on 07/16/2026.
		if (residentCandidateValid && !captureResidentOracle) {
			final double candidateFullRms = residentResult.candidateRms;   // float widened exactly
			final double candidatePureRms = residentResult.candidatePureRms;
			if (this.last_rms == null) {
				// Rung C1: resident prepare defers the initial RMS to the first
				// step's current full/pure RMS (packed[19,20]). By Claude on 07/17/2026.
				this.initial_rms = new double [] {residentResult.currentRms, residentResult.currentPureRms};
				this.last_rms = this.initial_rms.clone();
			}
			this.good_or_bad_rms = new double [] {candidateFullRms, candidatePureRms};
			if (residentResult.accepted) {
				rslt[0] = true;
				rslt[1] = residentResult.stop;
				this.last_rms = new double [] {candidateFullRms, candidatePureRms};
				this.parameters_vector = new_vector.clone();
			} else { // rejected: GPU kept the current set (no flip); nothing to restore here
				rslt[0] = false;
				rslt[1] = false;
			}
			return rslt;
		}
		double [] fx = getFxDerivs(
				new_vector, // double []         vector,
				last_jt,           // final double [][] jt, // should be null or initialized with [vector.length][]
				scenesCLT[1],      // final QuadCLT     scene_QuadClt,
				scenesCLT[0],      // final QuadCLT     reference_QuadClt,
				debug_level);      // final int         debug_level)
		double [] rms = new double[2];
		last_ymfx = getYminusFxWeighted(
//				vector_XYS, // final double [][] vector_XYS,
				fx, // final double []   fx,
				rms); // final double []   rms_fp // null or [2]
		if (debug_level > 2) {
			/*
			dbgYminusFx(this.last_ymfx, "next y-fX");
			dbgXY(new_vector, "XY-correction");
			*/
		}

		if (last_ymfx == null) {
			return null; // need to re-init/restart LMA
		}
		if (residentCandidateValid && (residentResult != null) && (debug_level >= 1)) {
			final boolean decisionsMatch = poseRmsDecisionsMatch(
					residentResult, this.last_rms, rms, rms_diff);
			if (!poseLmaAcceptanceOracleReported ||
					(!decisionsMatch && !poseLmaAcceptanceMismatchReported)) {
				reportPoseRmsDecision(
						decisionsMatch ? "" : " MISMATCH",
						residentResult, this.last_rms, rms, rms_diff);
			}
			if (!decisionsMatch) poseLmaAcceptanceMismatchReported = true;
			poseLmaAcceptanceOracleReported = true;
		}

		this.good_or_bad_rms = rms.clone();
		if (rms[0] < this.last_rms[0]) { // improved
			rslt[0] = true;
			rslt[1] = rms[0] >=(this.last_rms[0] * (1.0 - rms_diff));
			this.last_rms = rms.clone();

			this.parameters_vector = new_vector.clone();
			if (debug_level > 2) {
				// print vectors in some format
				/*
				System.out.print("delta: "+corr_delta.toString()+"\n");
				System.out.print("New vector: "+new_vector.toString()+"\n");
				System.out.println();
				*/
			}
		} else { // worsened
			rslt[0] = false;
			rslt[1] = false; // do not know, caller will decide
			// restore state
			fx = getFxDerivs(
					parameters_vector, // double []         vector,
					last_jt,           // final double [][] jt, // should be null or initialized with [vector.length][]
					scenesCLT[1],      // final QuadCLT     scene_QuadClt,
					scenesCLT[0],      // final QuadCLT     reference_QuadClt,
					debug_level);      // final int         debug_level)
			last_ymfx = getYminusFxWeighted(
					fx, // final double []   fx,
					this.last_rms); // final double []   rms_fp // null or [2]
			if (last_ymfx == null) {
				return null; // need to re-init/restart LMA
			}
			if (debug_level > 2) {
				/*
				 dbgJacobians(
							corr_vector, // GeometryCorrection.CorrVector corr_vector,
							1E-5, // double delta,
							true); //boolean graphic)
							*/
			}
		}
		return rslt;
	}
	
	private static double [][][] setEigenTransform(
			final double   eig_max_sqrt,
			final double   eig_min_sqrt,
			final double [][] eigen){ // [tilesX*tilesY]{lamb0_x,lamb0_y, lamb0, lamb1} eigenvector0[x,y],lam0,lam1
		if (eigen == null) {
			return null;
		}
		final double [][][] transform = new double[eigen.length][][];
		final Thread[] threads = ImageDtt.newThreadArray(QuadCLT.THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		final double k0 = 1.0/eig_max_sqrt;
		
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int iTile = ai.getAndIncrement(); iTile < eigen.length; iTile = ai.getAndIncrement()) if (eigen[iTile] != null){
						double [][] am = {{eigen[iTile][0],eigen[iTile][1]},{eigen[iTile][1],-eigen[iTile][0]}};
						if (!Double.isNaN(am[0][0]) && !Double.isNaN(am[0][1])) {
							transform[iTile] = new double [2][2];
							for (int i = 0; i < 2; i++) {
								double k = Math.max(0, 1/Math.max(eig_min_sqrt, Math.sqrt(eigen[iTile][2+i]))-k0);
								for (int j = 0; j < 2; j++) {
									transform[iTile][i][j] = k* am[i][j];  
								}		
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return transform;
	}
	
	private void setSamplesWeights(
			final double [][] vector_XYSDS,  // not regularized yet
			final double      min_confidence,// minimal tile confidence to use. Use 0 to disable
			final boolean []  reliable_ref,  // additional mask for vector_XYSDS or null
			final boolean same_weights)      // same weight if > 0
	{
		//num_components 2 - old, 3 - with disparity
		this.weights = new double [num_samples + parameters_vector.length];
		
		final Thread[] threads = ImageDtt.newThreadArray(QuadCLT.THREADS_MAX);
		final AtomicInteger ai = new AtomicInteger(0);
		final AtomicInteger ati = new AtomicInteger(0);
		final AtomicInteger anum_defined = new AtomicInteger(0);
		final double [] sw_arr = new double [threads.length];
		double sum_weights;
		final int num_types = (num_components > 2) ? 2 : 1;
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					int thread_num = ati.getAndIncrement();
					for (int iMTile = ai.getAndIncrement(); iMTile < vector_XYSDS.length; iMTile = ai.getAndIncrement())
						if ((vector_XYSDS[iMTile] != null) && ((reliable_ref == null) || reliable_ref[iMTile])){
							double w = vector_XYSDS[iMTile][2];
							if (w >= min_confidence) {
								if ((eig_trans != null) && (eig_trans[iMTile] == null)) {
									w = 0;
									continue;
								}
								if (Double.isNaN(w)) {
									w = 0;
									continue;
								}
								if (w > 0) {
									if (same_weights) {
										w = 1.0;
									}
									anum_defined.getAndIncrement();
									weights[num_components  * iMTile] = w;
									sw_arr[thread_num] += 2*w;
									//disparity_weight
									if (num_types > 1) {
										w = vector_XYSDS[iMTile][4] * disparity_weight;
										if (Double.isNaN(w) || Double.isNaN(vector_XYSDS[iMTile][3])) {
											w = 0;
											vector_XYSDS[iMTile][3] = 0.0; // disparity
										}
										weights[num_components * iMTile + 2] = w;
										sw_arr[thread_num] += w;
									}
								}
							}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		sum_weights = 0.0;
		for (double w:sw_arr) {
			sum_weights += w;
		}
		num_defined = anum_defined.get();
		this.sum_weights = sum_weights;
		
		if (sum_weights <= 1E-8) {
			System.out.println("!!!!!! setSamplesWeights(): sum_weights=="+sum_weights+" <= 1E-8");
		}
		ai.set(0);
		final double s = 1.0/sum_weights; // Was 0.5 - already taken care of
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int iMTile = ai.getAndIncrement(); iMTile < vector_XYSDS.length; iMTile = ai.getAndIncrement())
						if ((vector_XYSDS[iMTile] != null) && ((reliable_ref == null) || reliable_ref[iMTile])){
						weights[num_components * iMTile] *= s;
						weights[num_components * iMTile + 1] = weights[num_components * iMTile];
						if (num_components > 2) {
							weights[num_components * iMTile + 2] *= s;
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		pure_weight = 1.0;
	}

	private void normalizeWeights()
	{
		final Thread[] threads = ImageDtt.newThreadArray(QuadCLT.THREADS_MAX);
		final AtomicInteger ai =    new AtomicInteger(0);
		final AtomicInteger ati =    new AtomicInteger(0);
		final double [] sum_weight = new double [threads.length];
		ati.set(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					int thread_num = ati.getAndIncrement();
					for (int i = ai.getAndIncrement(); i < num_samples; i = ai.getAndIncrement()){
						if (Double.isNaN(weights[i])) {
							System.out.println("normalizeWeights(): weights["+i+"== NaN: 2");
							weights[i] = 0;
						}
						sum_weight[thread_num] += weights[i];
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		double sum_weight_pure = 0;
		for (double sw:sum_weight)	sum_weight_pure += sw;
		double full_weight = sum_weight_pure;
		for (int i = 0; i < par_indices.length; i++) {
			int indx =  num_samples + i;
			if (Double.isNaN(weights[indx])) {
				System.out.println("normalizeWeights(): weights["+indx+"] == NaN: 1.5, i="+i);
				weights[indx] = 0;
			}
			full_weight += weights[indx];
		}
		pure_weight = sum_weight_pure/full_weight;
		final double s = 1.0/full_weight;
		if (Double.isNaN(s)) {
			System.out.println("normalizeWeights(): s == NaN  : 2");
		}
		ai.set(0);
		for (int ithread = 0; ithread < threads.length; ithread++) {
			threads[ithread] = new Thread() {
				public void run() {
					for (int i = ai.getAndIncrement(); i < weights.length; i = ai.getAndIncrement()){
						weights[i] *= s;
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
	}
	
	
	/**
	 * Combine unmodified parameters with these ones, using parameters mask
	 * @param vector variable-length adjustable vector, corresponding to a parameter mask
	 * @return full parameters vector combining adjusted and fixed parameters
	 */
	private double [] getFullVector(double [] vector) {
		double [] full_vector = parameters_full.clone();
		for (int i = 0; i < par_indices.length; i++) {
			full_vector[par_indices[i]] = vector[i];
		}
		return full_vector;
	}
	
	
	// if eig_trans != null, transform derivatives, keep Fx
	
	public ImagePlus showFxDerivs(
			double []   fX,
			double [][] jt,
			int         num_rows, // <=0 - stack
			boolean     show_reg,  // extra row for regularization parameters
			boolean     show,
			String      title) {
		int gap =    1;
		double [][][] scales = {
				{{320,320}, {256,256} ,{0,1}},	//y                      *
				{{320,320}, {256,256} ,{0,1}},	//fX                     *
				{{ 0, 2.5},  { 0, 2.5} , {0,1}},	//y-fX                   *
				{{0,   75},  { 0, 75},  {0,1}},  //hor *
				{{0,   75},  { 0, 75},  {0,1}},  //vert *
				{{ 0,100},  { 0,100},  {0,1}},  //"pX",      // (pix)     0
				{{ 0,100},  { 0,100},  {0,1}},  //"pY",            // (pix)     1
				{{0,100},   { 0,100},  {0,1}},  //"disp",          // (pix)     2
				{{0,100},   { 0,100},  {0,1}},  //"ers_vaz_ref",   // (rad/sec) 3
				{{0,100},   { 0,100},  {0,1}},  //"ers_vtl_ref",   // (rad/sec) 4
				{{0,100},   { 0,100},  {0,1}},	//"ers_vrl_ref",   // (rad/sec) 5
				{{0,100},   { 0,100},  {0,1}}, //"ers_dvx_ref",   // (m/s)     6
				{{0,100},   { 0,100},  {0,1}}, //"ers_dvy_ref",   // (m/s)     7
				{{0,100},   { 0,100},  {0,1}}, //"ers_dvz_ref",   // (m/s)     8
				{{-1120,20},{ 70,50},  {0,1}}, //"azimuth_ref",   // (rad)     9 *
				{{-70,  50},{-1120,20},{0,1}}, //"tilt_ref",      // (rad)    10 *
				{{0,500},   { 0,500},  {0,1}}, //"roll_ref",      // (rad)    11 *
				{{ 25,2.5}, {-1.5,0.5},{0,1}}, //"X_ref",         // (m)      12 *
				{{-1.5,0.5},{-25,2.5}, {0,1}}, //"Y_ref",         // (m)	  13 *
				{{ 0, 7.5}, { 0, 7.5}, {0,1}}, //"Z_ref",         // (m)      14 *
				{{0,100},   { 0,100},  {0,1}}, //"ers_vaz_scene", // (rad/sec)15
				{{0,100},   { 0,100},  {0,1}}, //"ers_vtl_scene", // (rad/sec)16
				{{0,100},   { 0,100},  {0,1}}, //"ers_vrl_scene", // (rad/sec)17
				{{0,100},   { 0,100},  {0,1}}, //"ers_dvx_scene", // (m/s)    18
				{{0,100},   { 0,100},  {0,1}}, //"ers_dvy_scene", // (m/s)    19
				{{0,100},   { 0,100},  {0,1}}, //"ers_dvz_scene", // (m/s)    20
				{{1120,20}, { -70,50}, {0,1}}, //"azimuth_scene", // (rad)    21 *
				{{70,  50}, {1120,20}, {0,1}}, //"tilt_scene",   // (rad)    22 *
				{{0,500},   { 0,500},  {0,1}}, //"Roll_scene",    // (rad)    23 *
				{{-25, 2.5},{ 1.5,0.5},{0,1}}, //"X_scene",       // (m)      24 *
				{{ 1.5,0.5},{25, 2.5}, {0,1}}, //"Y_scene",       // (m)	  25 *
				{{ 0, 7.5}, { 0, 7.5},  {0,1}}  //"Z_scene"        // (m)      26 *
		};

		String [] titles_pre = {"y", "fx", "y-fx", "hor", "vert"};
		int num_pre = titles_pre.length;
		int length = num_samples/num_components;
		int width =  tilesX;
		int height0 = length/width;
		int height = height0 + (show_reg? 1 : 0);
		int num_pars0 =  jt.length;
		
		int num_pars1 =  jt.length+num_pre;
		double [][] jt_ext = new double [num_pars1][];
		jt_ext[0] = y_vector;
		jt_ext[1] = fX;
		jt_ext[2] = y_vector.clone();
		for (int i = 0; i < jt_ext[2].length; i++) {
			jt_ext[2][i] -= jt_ext[1][i];
		}
		double [][][] img_stack = new double [num_components][num_pars1][width*height];
		String [] titles = new String [num_pars1];
		for (int i = 0; i < num_pre; i++) {
			titles[i] = titles_pre[i];
		}
		
		for (int i = 0; i < par_indices.length; i++) {
			titles[i+num_pre] = ErsCorrection.DP_DERIV_NAMES[par_indices[i]];
			jt_ext[i+num_pre] = jt[i];
		}
		// combine rotate and translate 
		// See if both azimuth_scene and X_scene are present
		int [][] hor_vert_indices = {{-1,-1},{-1,-1}}; 
		for (int i = 0; i < par_indices.length; i++) {
			if (par_indices[i] ==ErsCorrection.DP_DSAZ) hor_vert_indices[0][0] = i;
			if (par_indices[i] ==ErsCorrection.DP_DSX)  hor_vert_indices[0][1] = i;
			if (par_indices[i] ==ErsCorrection.DP_DSTL) hor_vert_indices[1][0] = i;
			if (par_indices[i] ==ErsCorrection.DP_DSY)  hor_vert_indices[1][1] = i;
		}
		for (int vnh = 0; vnh < hor_vert_indices.length; vnh++) if (( hor_vert_indices[vnh][0] >=0 ) && (hor_vert_indices[vnh][1] >= 0)){
			double [] sw = new double[2],swd = new double[2];
			for (int n = 0; n < hor_vert_indices[vnh].length; n++) {
				for (int i = 0; i < length; i++) {
					int indx = num_components * i + vnh;
					double w = weights[indx];
					double d = jt[hor_vert_indices[vnh][n]][indx];
					sw [n]+=w;
					swd[n]+=w*d;
				}
				swd[n] /= sw[n];
			}
			double mult_second = swd[0] / swd[1];
			int jindex = 3 + vnh; 
			jt_ext[jindex] = new double [weights.length];
			for (int i = 0; i < weights.length; i++) {
				jt_ext[jindex][i] = jt[hor_vert_indices[vnh][0]][i] - mult_second * jt[hor_vert_indices[vnh][1]][i];
			}
		}
		String [] titles_top = new String [num_components];
		titles_top[0] = "pX";
		titles_top[1] = "pY";
		if (num_components > 2) {
			titles_top[2] = "Disp";
		}
		for (int np = 0; np < num_pars1; np++){
			for (int nc = 0; nc < num_components; nc++) {
				Arrays.fill(img_stack[nc][np], Double.NaN);
				if (jt_ext[np] != null) {
					for (int npix = 0; npix < length; npix++) {
						int sindx = nc+num_components*npix;
						if (weights[sindx] > 0) {
							if ((img_stack[nc]== null) || (img_stack[nc][np]== null) ||
									(jt_ext[np]== null)) {
								System.out.println("showFxDerivs(): Null pointer");

							}
							img_stack[nc][np][npix] = jt_ext[np][sindx]; // null pointer
						}
					}
					if (show_reg) {
						for (int i = 0; i < num_pars0; i++) {
							img_stack[nc][np][length+i] = jt_ext[np][num_samples+i]; 
						}
					}
				}
			}
		}
		if (num_rows <= 0) {
			ImagePlus imp_stack = ShowDoubleFloatArrays.showArraysHyperstack(
					img_stack,  // double[][][] pixels, 
					width,      // int          width, 
					title,      // "terrain_vegetation_render.tiff", // String       title, "time_derivs-rt"+diff_time_rt+"-rxy"+diff_time_rxy,
					titles,     // String []    titles, // all slices*frames titles or just slice titles or null
					titles_top, // String []    frame_titles, // frame titles or null
					show);      // boolean      show)
			return imp_stack;

		}
		// TODO: add translate+rotate combo and scale factors to see all tiles simultaneously (as static members?)
		int num_cols = (int) Math.ceil(1.0*num_pars1/num_rows); 
		int full_width =  num_cols * (width +  gap) - gap;
		int full_height = num_rows * (height + gap) - gap;
		double [][] tiled_stack = new double [num_components][full_width*full_height];
		for (int nc = 0; nc < tiled_stack.length; nc++) {
			Arrays.fill (tiled_stack[nc], Double.NaN);
		}
		for (int ntile = 0; ntile < num_pars1; ntile++) {
			int tile_row = ntile / num_cols; 
			int tile_col = ntile % num_cols;
			int tl_index = tile_row * (height + gap) * full_width + tile_col * (width + gap); 
			for (int nc = 0; nc < tiled_stack.length; nc++) {
				int scale_index = (ntile<num_pre)? ntile: (par_indices[ntile-num_pre] + num_pre);
				double offs = scales[scale_index][nc][0];
				double div =  scales[scale_index][nc][1];
				for (int nrow = 0; nrow < height; nrow++) {
					System.arraycopy(
							img_stack[nc][ntile],
							nrow * width,
							tiled_stack[nc],
							tl_index + nrow * full_width,
							width);
					if (nrow < height0) {
						for (int i = 0; i < width; i++) {
							int indx = tl_index + nrow * full_width + i;
							tiled_stack[nc][indx]= (tiled_stack[nc][indx]- offs)/div;
						}
					}
				}
			}			
		}
		ImagePlus imp_stack = ShowDoubleFloatArrays.makeArrays(
				tiled_stack, // double[][] pixels,
				full_width,  //int width,
				full_height, // int height,
				title+"_tiled",       // String title,
				titles_top); // String [] titles)
    	imp_stack.getProcessor().resetMinAndMax();
		if (show) {
			imp_stack.show();
		}
		return imp_stack;
	}
	
	public double [][] getFxDerivsDelta(
			double []         vector,
			final double[]    deltas, // same length as vector, or [1] - then use for all
			final QuadCLT     scene_QuadClt,
			final QuadCLT     reference_QuadClt,
			final int         debug_level) {
		int dbg_n = -2256; // 1698; // -2486;
		final boolean mb_mode = (weights == null); // mb_mode should have num_components==2 !
		final int weights_length = mb_mode ?  (2 * macrotile_centers.length) : weights.length; // OK to keep 2
		double [][] jt =  new double [vector.length][weights_length];
		for (int nv = 0; nv < vector.length; nv++) {
			double delta = (nv < deltas.length) ? deltas[nv] : deltas[deltas.length -1];
			if (nv == dbg_n) {
				System.out.println("getFxDerivsDelta(): nv="+nv);
			}
			double [] vpm = vector.clone();
			vpm[nv]+= 0.5*delta;
			double [] fx_p =  getFxDerivs(
					vpm,
					null,              // final double [][] jt, // should be null or initialized with [vector.length][]
					scene_QuadClt,     // final QuadCLT     scene_QuadClt,
					reference_QuadClt, // final QuadCLT     reference_QuadClt,
					debug_level);
			vpm[nv]-= delta;
			double [] fx_m =  getFxDerivs(
					vpm,
					null, // final double [][] jt, // should be null or initialized with [vector.length][]
					scene_QuadClt,     // final QuadCLT     scene_QuadClt,
					reference_QuadClt, // final QuadCLT     reference_QuadClt,
					debug_level);
			for (int i = 0; i <weights_length; i++) if ((weights==null) ||  (weights[i] > 0)) {
				jt[nv][i] = (fx_p[i]-fx_m[i])/delta;
			}
		}
		return jt;
	}
	
	public void debugDerivs(
			double []         vector,
			final QuadCLT     scene_QuadClt,
			final QuadCLT     reference_QuadClt,
			final double      delta,
			final boolean     show_img,
			final int         debugLevel) {
        double [] delta_scale ={
				1.0,  //"pX",            // (pix)     0
				1.0,  //"pY",            // (pix)     1
				1.0,  //"disp",          // (pix)     2
				1.0,  //"ers_vaz_ref",   // (rad/sec) 3
				1.0,  //"ers_vtl_ref",   // (rad/sec) 4
				1.0,  //"ers_vrl_ref",   // (rad/sec) 5
				1.0,  //"ers_dvx_ref",   // (m/s)     6
				1.0,  //"ers_dvy_ref",   // (m/s)     7
				1.0,  //"ers_dvz_ref",   // (m/s)     8
				1.0,  //"azimuth_ref",   // (rad)     9 *
				1.0,  //"tilt_ref",      // (rad)    10 *
				1.0,  //"roll_ref",      // (rad)    11 *
				1.0,  //"X_ref",         // (m)      12 *
				1.0,  //"Y_ref",         // (m)	  13 *
				1.0,  //"Z_ref",         // (m)      14 *
				1.0,  //"ers_vaz_scene", // (rad/sec)15
				1.0,  //"ers_vtl_scene", // (rad/sec)16
				1.0,  //"ers_vrl_scene", // (rad/sec)17
				1.0,  //"ers_dvx_scene", // (m/s)    18
				1.0,  //"ers_dvy_scene", // (m/s)    19
				1.0,  //"ers_dvz_scene", // (m/s)    20
				1.0,  //"azimuth_scene", // (rad)    21 *
				1.0,  //"tilt_scene",   // (rad)    22 *
				1.0,  //"Roll_scene",    // (rad)    23 *
				1.0,  //"X_scene",       // (m)      24 *
				1.0,  //"Y_scene",       // (m)	  25 *
				1.0   //"Z_scene"        // (m)      26 *
		};
//		int length = num_samples/num_components;
//		int width =  tilesX;
//		int height0 = length/width;
		double [] deltas = new double [vector.length];
		String [] par_names = new String[vector.length];
		for (int i = 0; i < deltas.length; i++) {
			deltas[i] = delta * delta_scale[par_indices[i]];
			par_names[i] =  ErsCorrection.DP_DERIV_NAMES[par_indices[i]];
		}
		String [] comp_names = {"dx", "dy","ddisp"};
		double [][] jt = new double [vector.length][];
		double [] fX = getFxDerivs(
				vector,            // final double []   vector,
				jt,                // final double [][] jt, // should be null or initialized with [vector.length][]
				scene_QuadClt,     // final QuadCLT     scene_QuadClt,
				reference_QuadClt, // final QuadCLT     reference_QuadClt,
				debugLevel);       // final int         debug_level)
		double  [][] jt_delta =  getFxDerivsDelta (
				vector,            // final double []   vector,
				deltas,            // final double[]    deltas, // same length as vector, or [1] - then use for all
				scene_QuadClt,     // final QuadCLT     scene_QuadClt,
				reference_QuadClt, // final QuadCLT     reference_QuadClt,
				debugLevel-10);    // final int         debug_level)
		double [][] jt_diff = new double [jt_delta.length][jt_delta[0].length];
		double max_err=0;
		double min_bad = 0.01;
		for (int n = 0; n < jt_diff.length; n++) {
			for (int w = 0; w < jt_diff[0].length; w++) if (weights[w] > 0){ // skip zero weights
				jt_diff[n][w] = jt[n][w] - jt_delta[n][w];
//				int [] ti = par_rindex[n];
				int ntile = w/num_components;
				int ncomp = w % num_components;
				if (Math.abs(jt_diff[n][w]) > max_err) {
					System.out.println("debugDerivs(): n="+n+" ("+par_names[n]+"), ntile = "+ntile +", comp="+comp_names[ncomp] +", diff="+jt_diff[n][w]+" jt="+jt[n][w]+" jt_delta="+jt_delta[n][w]);
				} else if (Math.abs(jt_diff[n][w]) > min_bad) {
					System.out.println("debugDerivs(): n="+n+" ("+par_names[n]+"), ntile = "+ntile +", comp="+comp_names[ncomp] +", diff="+jt_diff[n][w]+" jt="+jt[n][w]+" jt_delta="+jt_delta[n][w]);
				}
				max_err = Math.max(max_err, Math.abs(jt_diff[n][w]));
			}
		}
		System.out.println("delta = "+delta+", max_err = "+max_err);
		if (show_img) {
			String      dbg_title= scenesCLT[0].getImageName()+"-"+scenesCLT[1].getImageName();
			int         dbg_num_rows = 3;
			boolean     dbg_show_reg = true;
			boolean     dbg_show =     true;
			showFxDerivs(
					fX,           // double []   fX,
					jt,           // double [][] jt,
					0,            // int         num_rows, // <=0 - stack 3?
					dbg_show_reg, // boolean     show_reg,  // extra row for regularization parameters
					dbg_show,     // boolean     show,
					dbg_title+"-jt");   // String      title);
			showFxDerivs(
					fX,           // double []   fX,
					jt,           // double [][] jt,
					dbg_num_rows, // int         num_rows, // <=0 - stack 3?
					dbg_show_reg, // boolean     show_reg,  // extra row for regularization parameters
					dbg_show, // boolean     show,
					dbg_title+"-jt"); // String      title);
			
			showFxDerivs(
					fX,           // double []   fX,
					jt_delta,     // double [][] jt,
					0,            // int         num_rows, // <=0 - stack 3?
					dbg_show_reg, // boolean     show_reg,  // extra row for regularization parameters
					dbg_show,     // boolean     show,
					dbg_title+"-jt_delta_"+delta);   // String      title);
			showFxDerivs(
					fX,           // double []   fX,
					jt_delta,     // double [][] jt,
					dbg_num_rows, // int         num_rows, // <=0 - stack 3?
					dbg_show_reg, // boolean     show_reg,  // extra row for regularization parameters
					dbg_show, // boolean     show,
					dbg_title+"-jt_delta_"+delta); // String      title);
			
			showFxDerivs(
					fX,           // double []   fX,
					jt_diff,      // double [][] jt,
					0,            // int         num_rows, // <=0 - stack 3?
					dbg_show_reg, // boolean     show_reg,  // extra row for regularization parameters
					dbg_show,     // boolean     show,
					dbg_title+"-jt_diff_"+delta);   // String      title);
			showFxDerivs(
					fX,           // double []   fX,
					jt_diff,      // double [][] jt,
					dbg_num_rows, // int         num_rows, // <=0 - stack 3?
					dbg_show_reg, // boolean     show_reg,  // extra row for regularization parameters
					dbg_show, // boolean     show,
					dbg_title+"-jt_diff_"+delta); // String      title);
		}
		return;
	}
	
	
	private double [] getFxDerivs(
			double []         vector,
			final double [][] jt, // should be null or initialized with [vector.length][]
			final QuadCLT     scene_QuadClt,
			final QuadCLT     reference_QuadClt,
			final int         debug_level)
	{
		final double [] full_vector = getFullVector(vector);
		final ErsCorrection ers_ref =   reference_QuadClt.getErsCorrection();
		final ErsCorrection ers_scene = scene_QuadClt.getErsCorrection();
		final double [] scene_xyz =     new double[3];
		final double [] scene_atr  =    new double[3];
		final double [] reference_xyz = new double[3]; // will stay 0
		final double [] reference_atr = new double[3]; // will stay 0
		final boolean mb_mode = (weights == null); // mb_mode should have num_components==2 !
		final int weights_length = mb_mode ?  (2 * macrotile_centers.length) : weights.length; // OK to keep 2
		final double [] fx =       mb_mode ? null : (new double [weights_length]); // weights.length]; : weights.length :
		if (jt != null) {
			for (int i = 0; i < jt.length; i++) {
				jt[i] = new double [weights_length]; // weights.length];
			}
		}
		
		for (int i = 0; i <3; i++) {
			ers_ref.ers_watr_center_dt[i] =   full_vector[ErsCorrection.DP_DVAZ +  i];
			ers_ref.ers_wxyz_center_dt[i] =   full_vector[ErsCorrection.DP_DVX +   i];
			ers_scene.ers_watr_center_dt[i] = full_vector[ErsCorrection.DP_DSVAZ + i];
			ers_scene.ers_wxyz_center_dt[i] = full_vector[ErsCorrection.DP_DSVX +  i];
			reference_atr[i] = full_vector[ErsCorrection.DP_DAZ +  i];
			reference_xyz[i] = full_vector[ErsCorrection.DP_DX +  i];
			scene_atr[i] =     full_vector[ErsCorrection.DP_DSAZ +  i];
			scene_xyz[i] =     full_vector[ErsCorrection.DP_DSX +  i];
		}
		if (Double.isNaN(scene_xyz[0])) {
			System.out.println("getFxDerivs(): scene_xyz[0]=NaN");
			System.out.println();
		}
		// Rung C0 probe: setupERS pair, lean (non-MB) sites only. By Claude on 07/17/2026.
		final ProfileSink prof_sink_fx = mb_mode ? null : profileSink;
		final long prof_ers_t0 = (prof_sink_fx != null) ? System.nanoTime() : 0L;
		ers_scene.setupERS();
		ers_ref.setupERS();
		if (prof_sink_fx != null) {
			prof_sink_fx.add(PROF_FX_SETUPERS, System.nanoTime() - prof_ers_t0);
		}
		if (!mb_mode && fillFromPoseFxProvider(
				ers_ref, ers_scene,
				reference_xyz, reference_atr, scene_xyz, scene_atr,
				fx, jt)) {
			// Pull rows are fixed-size policy, not per-tile projection work.
			for (int i = 0; i < par_indices.length; i++) {
				fx[i + num_samples] = vector[i];
				if (jt != null) jt[i][i + num_samples] = 1.0;
			}
			return fx;
		}
		final Matrix [] reference_matrices_inverse = ErsCorrection.getInterRotDeriveMatrices(
				reference_atr, // double [] atr);
				true);         // boolean invert));

		final Matrix [] scene_matrices_inverse = ErsCorrection.getInterRotDeriveMatrices(
				scene_atr, // double [] atr);
				true);         // boolean invert));

		final Matrix  scene_rot_matrix = ErsCorrection.getInterRotDeriveMatrices(
				scene_atr, // double [] atr);
				false)[0]; // boolean invert));
		
		final Thread[] threads = ImageDtt.newThreadArray(QuadCLT.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 iMTile = ai.getAndIncrement(); iMTile < macrotile_centers.length; iMTile = ai.getAndIncrement()) {
//						if (iMTile == 1473) {
//							System.out.println("getFxDerivs(): iMTile="+iMTile);
//						}
						if ((macrotile_centers[iMTile]!=null) && (mb_mode || (weights[2*iMTile] > 0.0))){  // was: weights[iMTile]?
							//infinity_disparity
							boolean is_infinity = macrotile_centers[iMTile][2] < infinity_disparity;
							double [][] deriv_params = ers_ref.getDPxSceneDParameters(
									ers_scene,                   // ErsCorrection    ers_scene,
									true,                        // boolean correctDistortions,
									is_infinity,                 // boolean is_infinity,
									macrotile_centers[iMTile],   // double [] pXpYD_reference,
									reference_xyz,               // double [] reference_xyz, // reference (this) camera center in world coordinates
									scene_xyz,                   // double [] scene_xyz,    // (other, ers_scene) camera center in world coordinates
									reference_matrices_inverse,  // Matrix [] reference_matrices_inverse,
									scene_matrices_inverse,      // Matrix [] scene_matrices_inverse,
									scene_rot_matrix,            // Matrix    scene_rot_matrix,        // single rotation (direct) matrix for the scene
									debug_level);                // int debug_level);
							if (deriv_params!= null) {
								boolean bad_tile = false;
								if (!bad_tile) {
									if (!mb_mode) {
										fx[num_components * iMTile + 0] = deriv_params[0][0]; // pX
										fx[num_components * iMTile + 1] = deriv_params[0][1]; // pY
										if (num_components > 2) {
											fx[num_components * iMTile + 2] = deriv_params[0][2]; // D
										}
									}
									if (jt != null) {
										for (int i = 0; i < par_indices.length; i++) {
											int indx = par_indices[i] + 1;
											if (eig_trans != null) {
												//********************************
												// apply eig_trans here:
												for (int j = 0; j < 2; j++) {
													jt[i][num_components * iMTile + j] =
															eig_trans[iMTile][j][0] * deriv_params[indx][0] +
															eig_trans[iMTile][j][1] * deriv_params[indx][1];	
												}
											} else {
												jt[i][num_components * iMTile + 0] = deriv_params[indx][0]; // pX
												jt[i][num_components * iMTile + 1] = deriv_params[indx][1]; // pY (disparity is not used)
											}
											if (num_components > 2) {
												jt[i][num_components * iMTile + 2] = deriv_params[indx][2]; // pY (disparity is used)
											}
										}
										if (Double.isNaN(jt[0][num_components * iMTile + 0])) {
											System.out.println("getFxDerivs(): NaN, iMTile="+iMTile);
										}
									}
								}
							} else if (mb_mode) {
								if (jt != null) {
									for (int i = 0; i < par_indices.length; i++) {
										jt[i][2 * iMTile + 0] = Double.NaN; // pX
										jt[i][2 * iMTile + 1] = Double.NaN; // ; // pY (disparity is not used)
									}
								}
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		if (mb_mode) {
			return null;
		}
		// pull to the initial parameter values
		for (int i = 0; i < par_indices.length; i++) {
			fx [i + num_samples] = vector[i]; // - parameters_initial[i]; // scale will be combined with weights
			if (jt != null) { 
				jt[i][i + num_samples] = 1.0; // scale will be combined with weights
			}
		}
///		if (parameters_pull != null){
///			for (int i = 0; i < par_indices.length; i++) {
///				fx [i + num_samples] -= parameters_pull[i]; // - parameters_initial[i]; // scale will be combined with weights
///			}			
///		}
		return fx;
	}

	/**
	 * Use the primitive backend only for the frozen lean contract: two pixel
	 * components and the three scene-angle parameters. All general LMA shapes
	 * continue through the double Java implementation below.
	 */
	private boolean fillFromPoseFxProvider(
			final ErsCorrection ers_ref,
			final ErsCorrection ers_scene,
			final double [] reference_xyz,
			final double [] reference_atr,
			final double [] scene_xyz,
			final double [] scene_atr,
			final double [] fx,
			final double [][] jt) {
		if ((poseFxProvider == null) || (num_components != 2) ||
				(par_indices == null) || (par_indices.length != IntersceneLmaFloat.NUM_PARAMS)) return false;
		for (int par = 0; par < IntersceneLmaFloat.NUM_PARAMS; par++) {
			if (par_indices[par] != ErsCorrection.DP_DSAZ + par) return false;
		}
		if (poseFloatReference == null) poseFloatReference = IntersceneLmaFloat.Camera.capture(ers_ref);
		if (poseFloatScene == null) poseFloatScene = IntersceneLmaFloat.Camera.capture(ers_scene);
		if (poseFloatCenters == null) poseFloatCenters = IntersceneLmaFloat.flattenCenters(macrotile_centers);
		if (poseFloatSelection == null) {
			poseFloatSelection = new boolean [macrotile_centers.length];
			for (int tile = 0; tile < poseFloatSelection.length; tile++) {
				poseFloatSelection[tile] = (macrotile_centers[tile] != null) && (weights[2 * tile] > 0.0);
			}
		}
		// Rung C0 probe: the JNA fx/J roundtrip wall (camera-state H2D + kernel +
		// fx/jt/valid D2H), across ALL lean fx sites. By Claude on 07/17/2026.
		final ProfileSink prof_sink_jna = profileSink;
		final long prof_jna_t0 = (prof_sink_jna != null) ? System.nanoTime() : 0L;
		final IntersceneLmaFloat.Result result = poseFxProvider.calculate(
				poseFloatReference, poseFloatScene,
				IntersceneLmaFloat.toFloat3(reference_xyz), IntersceneLmaFloat.toFloat3(reference_atr),
				IntersceneLmaFloat.toFloat3(scene_xyz), IntersceneLmaFloat.toFloat3(scene_atr),
				poseFloatCenters, poseFloatSelection, jt != null);
		if (prof_sink_jna != null) {
			prof_sink_jna.add(PROF_FX_JNA, System.nanoTime() - prof_jna_t0);
		}
		if (result == null) return false;
		if ((result.fx == null) || (result.valid == null) ||
				(result.numTiles != macrotile_centers.length) ||
				(result.fx.length != 2 * result.numTiles) ||
				(result.valid.length != result.numTiles) ||
				((jt != null) && ((result.jt == null) ||
						(result.jt.length != IntersceneLmaFloat.NUM_PARAMS * 2 * result.numTiles)))) {
			throw new IllegalStateException("IntersceneLma: pose fx/J backend returned inconsistent arrays");
		}
		poseFloatLastResult = result;
		for (int tile = 0; tile < result.numTiles; tile++) {
			if (result.valid[tile] == 0) continue; // legacy geometry leaves zero rows for an unusable LMA tile
			final int vi = 2 * tile;
			fx[vi] = result.fx[vi];
			fx[vi + 1] = result.fx[vi + 1];
			if (jt == null) continue;
			for (int par = 0; par < IntersceneLmaFloat.NUM_PARAMS; par++) {
				final int ji = par * 2 * result.numTiles + vi;
				final float raw_x = result.jt[ji];
				final float raw_y = result.jt[ji + 1];
				if (eig_trans == null) {
					jt[par][vi] = raw_x;
					jt[par][vi + 1] = raw_y;
				} else {
					jt[par][vi] = eig_trans[tile][0][0] * raw_x + eig_trans[tile][0][1] * raw_y;
					jt[par][vi + 1] = eig_trans[tile][1][0] * raw_x + eig_trans[tile][1][1] * raw_y;
				}
			}
		}
		return true;
	}

	// Rung C0 (design 2026-07-16): lightweight profiling sink so the RT harness can
	// attribute prepareLMA sub-stages and the cross-cutting fx boundaries without
	// this class depending on the profiler. Null sink = zero overhead (no nanoTime).
	// By Claude on 07/17/2026.
	public interface ProfileSink { void add(int stage, long ns); }
	public static final int PROF_PREP_SETUP  = 0; // vector build + pull + setSamplesWeights
	public static final int PROF_PREP_FX1    = 1; // first getFxDerivs (weight normalization input)
	public static final int PROF_PREP_WJTJ   = 2; // getWJtJlambda + reg weights + normalizeWeights
	public static final int PROF_PREP_FX2    = 3; // second getFxDerivs (with regularization); y+RMS tail = derived
	public static final int PROF_FX_JNA      = 4; // poseFxProvider.calculate wall, ALL lean fx sites
	public static final int PROF_FX_SETUPERS = 5; // setupERS() pair wall, lean (non-MB) fx sites
	private ProfileSink profileSink = null;
	public void setProfileSink(final ProfileSink sink) { profileSink = sink; }

	// Rung C1 (design 2026-07-16): resident prepareLMA provider. The measured
	// inputs stay on the GPU; only camera state, pose vector, centers and policy
	// scalars cross H2D, and the 8-float compact result crosses D2H.
	// By Claude on 07/17/2026.
	public interface PoseLmaPrepareProvider {
		IntersceneLmaFloat.PrepareResidentResult calculate(
				IntersceneLmaFloat.Camera reference,
				IntersceneLmaFloat.Camera scene,
				float [] referenceXyz, float [] referenceAtr,
				float [] sceneXyz, float [] sceneAtr,
				float [] centers,                    // null = keep resident (per-sequence)
				int numTiles,                        // rung C2 // By Claude on 07/17/2026
				boolean useEigen, float eigMinSqrt, float eigMaxSqrt,
				float minConfidence, boolean sameWeights,
				float [] pullTargets, float [] paramRegweights,
				boolean light, // rung C3: frozen conditioning, fresh measured offsets only // By Claude on 07/17/2026
				boolean capture);
	}
	private PoseLmaPrepareProvider poseLmaPrepareProvider = null;
	public void setPoseLmaPrepareProvider(final PoseLmaPrepareProvider provider) {
		poseLmaPrepareProvider = provider;
	}
	private IntersceneLmaFloat.PrepareResidentResult poseResidentPrepare = null;
	private static boolean poseLmaPrepareOracleReported = false; // static like the other one-shot flags: IntersceneLma is per-scene (was instance -> 497 capture cycles) // By Claude on 07/17/2026
	// Rung C2 "configuration registers" (design 2026-07-16): per-SEQUENCE state -
	// the virtual-center reference camera is static by construction (zero pose,
	// zero rates) and the tile-centers grid is fixed - keyed on the reference
	// ErsCorrection identity and reset explicitly at sequence start. Per-SCENE
	// state (scene camera: ERS rates change per scene, never across cycles - only
	// the 3 angles do, and those travel in pose_vectors) lives in the per-scene
	// instance. Single-threaded pose loop - no synchronization. By Claude on 07/17/2026.
	private static Object poseSeqKey = null;
	private static IntersceneLmaFloat.Camera poseSeqRefCamera = null;
	private static float [] poseSeqCenters = null;
	public static void resetPoseSequenceCache() {
		poseSeqKey = null;
		poseSeqRefCamera = null;
		poseSeqCenters = null;
	}
	private IntersceneLmaFloat.Camera poseResidentSceneCamera = null; // per-scene; NOT cleared by resetPoseFxInputs
	// Rung C3b (Andrey 07/16, after the C3 gate FAILED): conditioning freezes at
	// CYCLE 2, not cycle 1. The first outer cycle absorbs nearly the whole
	// prediction error (single near-GN inner step, lambda=1e-3), so the cycle-1
	// peaks are measured at the blurriest pose - freezing them regressed azimuth
	// RMS +48%, roll +100%, NOT-settled 51->267. The cycle-2 measurement is at an
	// essentially converged pose = converged-quality eigen/weights.
	// By Claude on 07/16/2026.
	private int poseResidentFullPrepares = 0; // full resident prepares this scene (instance = per-scene)
	private int poseFreezeCycles = 0; // 0 = never freeze (per-cycle re-derivation; C3/C3b freeze FALSIFIED 07/16) // By Claude on 07/16/2026
	public void setPoseFreezeCycles(final int cycles) { poseFreezeCycles = cycles; }

	/** Run the resident GPU prepare at the current parameters_vector (mirrors the
	 *  getFxDerivs head: ERS pokes + setupERS + camera/centers capture). On
	 *  success the resident state is live: weights/y/eigen stay on the GPU and
	 *  the steps run with no per-step H2D. By Claude on 07/17/2026. */
	private boolean residentPrepare(
			final double [][] eigen_src,
			final double      eig_max_sqrt,
			final double      eig_min_sqrt,
			final double      min_confidence,
			final boolean     same_weights,
			final double []   param_regweights,
			final IntersceneLmaFloat.PrepareResidentResult frozen, // rung C3: non-null = cycle-1 conditioning to reuse (light) // By Claude on 07/17/2026
			final boolean     capture) {
		if (poseLmaPrepareProvider == null) return false;
		final boolean lightPrepare = (frozen != null) && !capture;
		final double [] full_vector = getFullVector(parameters_vector);
		final ErsCorrection ers_ref =   scenesCLT[0].getErsCorrection();
		final ErsCorrection ers_scene = scenesCLT[1].getErsCorrection();
		final double [] scene_xyz =     new double[3];
		final double [] scene_atr  =    new double[3];
		final double [] reference_xyz = new double[3];
		final double [] reference_atr = new double[3];
		for (int i = 0; i < 3; i++) { // pose values for pose_vectors: every call (cheap)
			reference_atr[i] = full_vector[ErsCorrection.DP_DAZ +  i];
			reference_xyz[i] = full_vector[ErsCorrection.DP_DX +  i];
			scene_atr[i] =     full_vector[ErsCorrection.DP_DSAZ +  i];
			scene_xyz[i] =     full_vector[ErsCorrection.DP_DSX +  i];
		}
		// Rung C2: reference camera + centers = per-SEQUENCE (virtual center is
		// static); scene camera = per-SCENE (ERS rates constant across cycles;
		// only the 3 angles change and they travel in pose_vectors). setupERS +
		// Camera.capture run once per level, and the provider gets null for
		// groups already resident. By Claude on 07/17/2026.
		final boolean newSequence = (poseSeqKey != ers_ref);
		if (newSequence) {
			for (int i = 0; i < 3; i++) {
				ers_ref.ers_watr_center_dt[i] = full_vector[ErsCorrection.DP_DVAZ + i];
				ers_ref.ers_wxyz_center_dt[i] = full_vector[ErsCorrection.DP_DVX +  i];
			}
			ers_ref.setupERS();
			poseSeqRefCamera = IntersceneLmaFloat.Camera.capture(ers_ref);
			poseSeqCenters = IntersceneLmaFloat.flattenCenters(macrotile_centers);
			poseSeqKey = ers_ref;
		}
		final boolean newScene = (poseResidentSceneCamera == null);
		if (newScene) {
			for (int i = 0; i < 3; i++) {
				ers_scene.ers_watr_center_dt[i] = full_vector[ErsCorrection.DP_DSVAZ + i];
				ers_scene.ers_wxyz_center_dt[i] = full_vector[ErsCorrection.DP_DSVX +  i];
			}
			ers_scene.setupERS();
			poseResidentSceneCamera = IntersceneLmaFloat.Camera.capture(ers_scene);
		}
		float [] pull = null;
		float [] regw = null;
		if (!lightPrepare) { // rung C3: pull/reg are part of the frozen conditioning
			pull = new float [IntersceneLmaFloat.NUM_PARAMS];
			regw = new float [IntersceneLmaFloat.NUM_PARAMS];
			for (int par = 0; par < IntersceneLmaFloat.NUM_PARAMS; par++) {
				pull[par] = (float) ((parameters_pull != null) ?
						parameters_pull[par] : parameters_vector[par]); // no pull = zero residual rows
				regw[par] = (float) param_regweights[par_indices[par]];
			}
		}
		final IntersceneLmaFloat.PrepareResidentResult result = poseLmaPrepareProvider.calculate(
				newSequence ? poseSeqRefCamera : null,
				newScene ? poseResidentSceneCamera : null,
				IntersceneLmaFloat.toFloat3(reference_xyz), IntersceneLmaFloat.toFloat3(reference_atr),
				IntersceneLmaFloat.toFloat3(scene_xyz), IntersceneLmaFloat.toFloat3(scene_atr),
				newSequence ? poseSeqCenters : null,
				macrotile_centers.length, (eigen_src != null),
				(float) eig_min_sqrt, (float) eig_max_sqrt,
				(float) min_confidence, same_weights, pull, regw, lightPrepare, capture);
		if ((result == null) || !result.valid) return false;
		if (lightPrepare) {
			// Rung C3/C3b: keep the frozen compact result (pure_weight/num_meas/reg
			// stay authoritative for the rest of the scene). By Claude on 07/17/2026.
			poseResidentPrepare = frozen;
		} else {
			poseResidentPrepare = result;
			pure_weight = result.pureWeight;
			num_defined = result.numMeas;
			sum_weights = result.sumWRaw;
			poseResidentFullPrepares++; // C3b: freeze only after the 2nd full prepare // By Claude on 07/16/2026
		}
		return true;
	}

	private boolean isPoseLmaStepShape() {
		if ((num_components != 2) || (par_indices == null) ||
				(par_indices.length != IntersceneLmaFloat.NUM_PARAMS) ||
				(last_jt == null) || (last_jt.length != IntersceneLmaFloat.NUM_PARAMS)) return false;
		for (int par = 0; par < IntersceneLmaFloat.NUM_PARAMS; par++) {
			if (par_indices[par] != ErsCorrection.DP_DSAZ + par) return false;
		}
		return true;
	}

	private static float [] toFloatArray(final double [] source) {
		final float [] result = new float [source.length];
		for (int i = 0; i < source.length; i++) result[i] = (float) source[i];
		return result;
	}

	private static float [] flattenJtFloat(final double [][] source) {
		final int length = source[0].length;
		final float [] result = new float [source.length * length];
		for (int par = 0; par < source.length; par++) {
			if ((source[par] == null) || (source[par].length != length)) {
				throw new IllegalArgumentException("IntersceneLma: inconsistent Jacobian rows");
			}
			for (int value = 0; value < length; value++) {
				result[par * length + value] = (float) source[par][value];
			}
		}
		return result;
	}

	private static float [] flattenEigenTransform(
			final double [][][] source,
			final int numTiles) {
		final float [] result = new float [4 * numTiles];
		if (source == null) {
			for (int tile = 0; tile < numTiles; tile++) {
				result[4 * tile] = 1.0f;
				result[4 * tile + 3] = 1.0f;
			}
			return result;
		}
		if (source.length != numTiles) {
			throw new IllegalArgumentException("IntersceneLma: eigen transform tile count");
		}
		for (int tile = 0; tile < numTiles; tile++) {
			if (source[tile] == null) continue;
			if ((source[tile].length != 2) || (source[tile][0] == null) ||
					(source[tile][1] == null) || (source[tile][0].length != 2) ||
					(source[tile][1].length != 2)) {
				throw new IllegalArgumentException("IntersceneLma: eigen transform["+tile+"]");
			}
			result[4 * tile] = (float) source[tile][0][0];
			result[4 * tile + 1] = (float) source[tile][0][1];
			result[4 * tile + 2] = (float) source[tile][1][0];
			result[4 * tile + 3] = (float) source[tile][1][1];
		}
		return result;
	}

	private static void reportPoseStepVsDouble(
			final String label,
			final IntersceneLmaFloat.LmaStepResult poseStep,
			final double [] normalProducts,
			final double [] delta,
			final double [] candidate) {
		float maxProducts = 0.0f;
		if ((normalProducts != null) && (normalProducts.length == poseStep.products.length)) {
			for (int i = 0; i < normalProducts.length; i++) {
				maxProducts = Math.max(maxProducts,
						Math.abs(poseStep.products[i] - (float) normalProducts[i]));
			}
		}
		float maxDelta = 0.0f;
		float maxCandidate = 0.0f;
		for (int i = 0; i < IntersceneLmaFloat.NUM_PARAMS; i++) {
			maxDelta = Math.max(maxDelta, Math.abs(poseStep.delta[i] - (float) delta[i]));
			maxCandidate = Math.max(maxCandidate,
					Math.abs(poseStep.candidate[i] - (float) candidate[i]));
		}
		System.out.println(String.format(
				"IntersceneLma %s vs Java-double runLma candidate: valid=%s, max|H,b|=%g, max|delta|=%g, max|candidate|=%g",
				label, poseStep.valid, maxProducts, maxDelta, maxCandidate));
	}

	private static boolean poseRmsDecisionsMatch(
			final IntersceneLmaFloat.ResidentLmaStepResult resident,
			final double [] javaCurrent,
			final double [] javaCandidate,
			final double rmsDiff) {
		final boolean doubleImproved = javaCandidate[0] < javaCurrent[0];
		final boolean doubleStop = doubleImproved &&
				(javaCandidate[0] >= javaCurrent[0] * (1.0 - rmsDiff));
		return Float.isFinite(resident.currentRms) &&
				Float.isFinite(resident.currentPureRms) &&
				Float.isFinite(resident.candidateRms) &&
				Float.isFinite(resident.candidatePureRms) &&
				(resident.accepted == doubleImproved) && (resident.stop == doubleStop);
	}

	private static void reportPoseRmsDecision(
			final String suffix,
			final IntersceneLmaFloat.ResidentLmaStepResult resident,
			final double [] javaCurrent,
			final double [] javaCandidate,
			final double rmsDiff) {
		final boolean finite = Float.isFinite(resident.currentRms) &&
				Float.isFinite(resident.currentPureRms) &&
				Float.isFinite(resident.candidateRms) &&
				Float.isFinite(resident.candidatePureRms);
		final boolean doubleImproved = javaCandidate[0] < javaCurrent[0];
		final boolean doubleStop = doubleImproved &&
				(javaCandidate[0] >= javaCurrent[0] * (1.0 - rmsDiff));
		System.out.println(String.format(
				"IntersceneLma resident CUDA-float vs Java-double RMS/decision%s: finite=%s, |current RMS|=%g, |current pure RMS|=%g, |candidate RMS|=%g, |candidate pure RMS|=%g, improved-match=%s, stop-match=%s",
				suffix,
				finite,
				Math.abs(resident.currentRms - javaCurrent[0]),
				Math.abs(resident.currentPureRms - javaCurrent[1]),
				Math.abs(resident.candidateRms - javaCandidate[0]),
				Math.abs(resident.candidatePureRms - javaCandidate[1]),
				resident.accepted == doubleImproved,
				resident.stop == doubleStop));
	}

	private static final class DoubleLmaStepResult {
		final double [] products;
		final double [] delta;
		final double [] candidate;

		DoubleLmaStepResult(
				final double [] products,
				final double [] delta,
				final double [] candidate) {
			this.products = products;
			this.delta = delta;
			this.candidate = candidate;
		}
	}

	/**
	 * General Java-double/Jama candidate path. The resident lean pose shape calls
	 * this only for its one-shot debug oracle or as a safety fallback; all other
	 * LMA shapes retain it as their production implementation.
	 */
	private DoubleLmaStepResult calculateDoubleLmaStep(
			final double lambda,
			final int debugLevel) {
		Matrix wjtjlambda;
		Matrix jty;
		double [] normalProducts = null;
		if (normalEquationProvider != null) {
			final int numPars = this.last_jt.length;
			normalProducts = normalEquationProvider.products(
					this.weights, this.last_jt, this.last_ymfx);
			if ((normalProducts == null) || (normalProducts.length != numPars * numPars + numPars)) {
				throw new IllegalStateException("IntersceneLma: normal-equation provider returned "+
						((normalProducts == null) ? "null" : normalProducts.length)+" values, expected "+
						(numPars * numPars + numPars));
			}
			final double [][] h = new double [numPars][numPars];
			for (int i = 0; i < numPars; i++) {
				System.arraycopy(normalProducts, i * numPars, h[i], 0, numPars);
				h[i][i] += h[i][i] * lambda; // same LMA convention as getWJtJlambda()
			}
			final double [] b = new double [numPars];
			System.arraycopy(normalProducts, numPars * numPars, b, 0, numPars);
			wjtjlambda = new Matrix(h);
			jty = new Matrix(b, b.length);
		} else {
			final Matrix yMinusFxWeighted = new Matrix(this.last_ymfx, this.last_ymfx.length);
			wjtjlambda = new Matrix(getWJtJlambda(lambda, this.last_jt));
			jty = (new Matrix(this.last_jt)).times(yMinusFxWeighted);
		}

		if (debugLevel > 2) {
			System.out.println("JtJ + lambda*diag(JtJ");
			wjtjlambda.print(18, 6);
		}
		final Matrix jtjlInv;
		try {
			jtjlInv = wjtjlambda.inverse();
		} catch (RuntimeException e) {
			if (debugLevel > 0) System.out.println("Singular Matrix!");
			return null;
		}
		if (debugLevel > 2) {
			System.out.println("(JtJ + lambda*diag(JtJ).inv()");
			jtjlInv.print(18, 6);
			System.out.println("Jt * (y-fx)");
			jty.print(18, 6);
		}
		final Matrix mdelta = jtjlInv.times(jty);
		if (debugLevel > 2) {
			System.out.println("mdelta");
			mdelta.print(18, 10);
		}
		final double [] delta = mdelta.getColumnPackedCopy();
		final double [] candidate = parameters_vector.clone();
		for (int i = 0; i < candidate.length; i++) candidate[i] += delta[i];
		return new DoubleLmaStepResult(normalProducts, delta, candidate);
	}

	private double [][] getWJtJlambda( // USED in lwir
			final double      lambda,
			final double [][] jt)
	{
		final int num_pars = jt.length;
		final int num_pars2 = num_pars * num_pars;
		final int nup_points = jt[0].length;
		final double [][] wjtjl = new double [num_pars][num_pars];
		final Thread[] threads = ImageDtt.newThreadArray(QuadCLT.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 indx = ai.getAndIncrement(); indx < num_pars2; indx = ai.getAndIncrement()) {
						int i = indx / num_pars;
						int j = indx % num_pars;
						if (j >= i) {
							double d = 0.0;
							for (int k = 0; k < nup_points; k++) {
								if (jt[i][k] != 0) {
									d+=0; // ???
								}
								d += weights[k]*jt[i][k]*jt[j][k];
								if (Double.isNaN(d)) {
									System.out.println("IntersceneLMA.getWJtJlambda(): NAN i="+i+", j="+j+", k="+k);
								}
								
							}
							wjtjl[i][j] = d;
							if (i == j) {
								wjtjl[i][j] += d * lambda;
							} else {
								wjtjl[j][i] = d;
							}
						}
					}
				}
			};
		}		      
		ImageDtt.startAndJoin(threads);
		return wjtjl;
	}
    	
	public void showDebugImage(	String dbg_title ) { // includes "_iteration_number" or "_final"
		if (s_vector == null) {
			return;
		}
		int tilesY =  s_vector.length / tilesX;
		String [] titles = {"dx","dy","dr", "str", "e0", "e1", "e"};
		double [][] dbg_img = new double [titles.length][tilesX*tilesY];
		for (int l = 0; l < dbg_img.length; l++) {
			Arrays.fill(dbg_img[l], Double.NaN);
		}
		double [] fx = getFxDerivs(
				parameters_vector, // double []         vector,
				null,              // final double [][] jt, // should be null or initialized with [vector.length][]
				scenesCLT[1],      // final QuadCLT     scene_QuadClt,
				scenesCLT[0],      // final QuadCLT     reference_QuadClt,
				-1);               // debug_level);      // final int         debug_level)
		double [] ymfxw_m = getYminusFxWeighted(
				fx,     // final double []   fx,
				null,   // final double []   rms_fp // null or [2]
				true);//final boolean     force_metric // when true, ignore transform with eig_trans and use linear dx, dy in pixels
        double [] ymfxw_e = null;
        if (eig_trans != null) {
        	ymfxw_e = getYminusFxWeighted(
    				fx,     // final double []   fx,
    				null,   // final double []   rms_fp // null or [2]
    				false);//final boolean     force_metric // when true, ignore transform with eig_trans and use linear dx, dy in pixels
        }
        for (int nTile = 0; nTile < s_vector.length; nTile++) {
        	int indx = num_components * nTile;
        	double w = weights[num_components * nTile];
        	if ((weights[indx] > 0) && (weights[indx+1] > 0)) {
        		for (int i = 0; i < 2; i++) {
        			ymfxw_m[indx + i] /= weights[indx + i];
        			if (ymfxw_e != null) {
            			ymfxw_e[indx + i] /= weights[indx + i];
        			}
        		}
        		double dx = ymfxw_m[indx + 0]; 
        		double dy = ymfxw_m[indx + 1]; 
        		dbg_img[0][nTile] = dx; 
        		dbg_img[1][nTile] = dy;
        		dbg_img[2][nTile] = Math.sqrt(dx*dx+dy*dy);
        		dbg_img[3][nTile] = s_vector[nTile];
    			if (ymfxw_e != null) {
            		double d0 = ymfxw_e[indx + 0]; 
            		double d1 = ymfxw_e[indx + 1]; 
            		dbg_img[4][nTile] = d0; 
            		dbg_img[5][nTile] = d1;
            		dbg_img[6][nTile] = Math.sqrt(d0*d0+d1*d1);
    			}        	
        	}
        }
		if (ymfxw_e == null) {
			dbg_img[4]=null;
			dbg_img[5]=null;
			dbg_img[6]=null;
		}
		ShowDoubleFloatArrays.showArrays( // out of boundary 15
				dbg_img,
				tilesX,
				tilesY,
				true,
				dbg_title,
				titles);
		
	}
	
	
	public boolean isEigenNormalized() {
		return (eig_trans != null);
	}
	public double [] calcRMS (boolean metric) {
		double [] rms_fp = new double [2];
		double [] fx = getFxDerivs(
				parameters_vector, // double []         vector,
				null,              // final double [][] jt, // should be null or initialized with [vector.length][]
				scenesCLT[1],      // final QuadCLT     scene_QuadClt,
				scenesCLT[0],      // final QuadCLT     reference_QuadClt,
				-1);               // debug_level);      // final int         debug_level)
		last_ymfx = getYminusFxWeighted(
				fx,     // final double []   fx,
				rms_fp, // final double []   rms_fp // null or [2]
				metric);//final boolean     force_metric // when true, ignore transform with eig_trans and use linear dx, dy in pixels
		return rms_fp;
	}
	
	
	private double [] getYminusFxWeighted(
			final double []   fx,
			final double []   rms_fp) { // null or [2]
		return getYminusFxWeighted(
				fx,     // final double []   fx,
				rms_fp, // final double []   rms_fp, // null or [2]
				false); // final boolean     force_metric)			

	}

	private double [] getYminusFxWeighted(
			final double []   fx,
			final double []   rms_fp, // null or [2]
			final boolean     force_metric // when true, ignore transform with eig_trans and use linear dx, dy in pixels			
			) {
		double [] ymfxw;
		if (thread_invariant) {
			 ymfxw =  getYminusFxWeightedInvariant(fx,rms_fp, force_metric); // null or [2]
		} else {
			 ymfxw = getYminusFxWeightedFast      (fx,rms_fp, force_metric); // null or [2]
		}
		return ymfxw; 
	}
	
	
	// TODO: modify these 2 methods to iterate through pairs, and transform pair if (eig_trans != null)
	
	private double [] getYminusFxWeightedInvariant(
			final double []   fx,
			final double []   rms_fp, // null or [2]
			final boolean     force_metric // when true, ignore transform with eig_trans and use linear dx, dy in pixels			
			) {
		final Thread[]      threads =     ImageDtt.newThreadArray(QuadCLT.THREADS_MAX);
		final AtomicInteger ai =          new AtomicInteger(0);
		final double []     wymfw =       new double [fx.length];
		double s_rms; 
		final double [] l2_arr = new double [num_samples]; 
		if (!force_metric && (eig_trans != null)) {
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					public void run() {
						for (int nTile = ai.getAndIncrement(); nTile < macrotile_centers.length; nTile = ai.getAndIncrement()) {
							int ix = num_components * nTile;
							if (weights[ix] > 0) {
								int iy = ix+1;
								double dx = y_vector[ix] - fx[ix];
								double dy = y_vector[iy] - fx[iy];
								double d0 = eig_trans[nTile][0][0] * dx + eig_trans[nTile][0][1] * dy;
								double d1 = eig_trans[nTile][1][0] * dx + eig_trans[nTile][1][1] * dy;
								double wd0 = d0 * weights[ix];
								double wd1 = d1 * weights[ix];
								if (Double.isNaN(wd0) || Double.isNaN(wd1)) {
									System.out.println("getYminusFxWeighted(): weights["+ix+"]="+weights[ix]+", wd0="+wd0+
											", y_vector[ix]="+y_vector[ix]+", fx[ix]="+fx[ix]);
									System.out.println("getYminusFxWeighted(): weights["+iy+"]="+weights[iy]+", wd1="+wd1+
											", y_vector[iy]="+y_vector[iy]+", fx[iy]="+fx[iy]);
								}
								l2_arr[ix] = d0 * wd0;
								wymfw[ix] = wd0;
								l2_arr[iy] = d1 * wd1;
								wymfw[iy] = wd1;
								for (int j = 2; j < num_components; j++) {
									int i = ix+j;
									double d = y_vector[i] - fx[i];
									double wd = d * weights[i];
									if (Double.isNaN(wd)) {
										System.out.println("getYminusFxWeighted(): weights["+i+"]="+weights[i]+", wd="+wd+
												", y_vector[i]="+y_vector[i]+", fx[i]="+fx[i]);
									}
									//double l2 = d * wd;
									l2_arr[i] = d * wd;
									wymfw[i] = wd;
								}
							}
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
		} else {
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					public void run() {
						for (int i = ai.getAndIncrement(); i < num_samples; i = ai.getAndIncrement()) if (weights[i] > 0) {
							double d = y_vector[i] - fx[i];
							double wd = d * weights[i];
							if (Double.isNaN(wd)) {
								System.out.println("getYminusFxWeighted(): weights["+i+"]="+weights[i]+", wd="+wd+
										", y_vector[i]="+y_vector[i]+", fx[i]="+fx[i]);
							}
							//double l2 = d * wd;
							l2_arr[i] = d * wd;
							wymfw[i] = wd;
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
		} 
		s_rms = 0.0;
		for (double l2:l2_arr) {
			s_rms += l2;
		}
		double rms_pure = Math.sqrt(s_rms/pure_weight);
		for (int i = 0; i < par_indices.length; i++) {
			int indx = i + num_samples;
			double d = y_vector[indx] - fx[indx]; // fx[indx] == vector[i]
			double wd = d * weights[indx];
			s_rms += d * wd;
			wymfw[indx] =   wd;
		}
		double rms = Math.sqrt(s_rms); // assuming sum_weights == 1.0; /pure_weight); shey should be re-normalized after adding regularization
		if (rms_fp != null) {
			rms_fp[0] = rms;
			rms_fp[1] = rms_pure;
		}
		return wymfw;
	}

	
	
	private double [] getYminusFxWeightedFast( // problems. at least with eigen?
			final double []   fx,
			final double []   rms_fp, // null or [2]
			final boolean     force_metric // when true, ignore transform with eig_trans and use linear dx, dy in pixels			
			) {
		final Thread[]      threads =     ImageDtt.newThreadArray(QuadCLT.THREADS_MAX);
		final AtomicInteger ai =          new AtomicInteger(0);
		final double []     wymfw =       new double [fx.length];
		final AtomicInteger ati = new AtomicInteger(0);
		final double [] l2_arr = new double [threads.length];
		if (!force_metric && (eig_trans != null)) {
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					public void run() {
						int thread_num = ati.getAndIncrement();
						for (int nTile = ai.getAndIncrement(); nTile < macrotile_centers.length; nTile = ai.getAndIncrement()) {
							int ix = num_components * nTile;
							if (weights[ix] > 0) {
								int iy = ix+1;
								double dx = y_vector[ix] - fx[ix];
								double dy = y_vector[iy] - fx[iy];
								double d0 = eig_trans[nTile][0][0] * dx + eig_trans[nTile][0][1] * dy;
								double d1 = eig_trans[nTile][1][0] * dx + eig_trans[nTile][1][1] * dy;
								double wd0 = d0 * weights[ix];
								double wd1 = d1 * weights[ix];
								if (Double.isNaN(wd0) || Double.isNaN(wd1)) {
									System.out.println("getYminusFxWeighted(): weights["+ix+"]="+weights[ix]+", wd0="+wd0+
											", y_vector[ix]="+y_vector[ix]+", fx[ix]="+fx[ix]);
									System.out.println("getYminusFxWeighted(): weights["+iy+"]="+weights[iy]+", wd1="+wd1+
											", y_vector[iy]="+y_vector[iy]+", fx[iy]="+fx[iy]);
								}
								l2_arr[thread_num] += d0 * wd0;
								wymfw[ix] = wd0;
								l2_arr[thread_num] += d1 * wd1;
								wymfw[iy] = wd1;
								for (int j = 2; j < num_components; j++) {
									int i = ix+j;
									double d = y_vector[i] - fx[i];
									double wd = d * weights[i];
									if (Double.isNaN(wd)) {
										System.out.println("getYminusFxWeighted(): weights["+i+"]="+weights[i]+", wd="+wd+
												", y_vector[i]="+y_vector[i]+", fx[i]="+fx[i]);
									}
									//double l2 = d * wd;
									l2_arr[thread_num] += d * wd;
									wymfw[i] = wd;
								}
							}
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
		} else {
			for (int ithread = 0; ithread < threads.length; ithread++) {
				threads[ithread] = new Thread() {
					public void run() {
						int thread_num = ati.getAndIncrement();
						for (int i = ai.getAndIncrement(); i < num_samples; i = ai.getAndIncrement()) if (weights[i] > 0) {
							double d = y_vector[i] - fx[i];
							double wd = d * weights[i];
							if (Double.isNaN(wd)) {
								System.out.println("getYminusFxWeighted(): weights["+i+"]="+weights[i]+", wd="+wd+
										", y_vector[i]="+y_vector[i]+", fx[i]="+fx[i]);
							}
							l2_arr[thread_num] += d * wd;
							wymfw[i] = wd;
						}
					}
				};
			}		      
			ImageDtt.startAndJoin(threads);
		}
		double s_rms = 0.0;
		for (double l2:l2_arr) {
			s_rms += l2;
		}
		double rms_pure = Math.sqrt(s_rms/pure_weight);
		for (int i = 0; i < par_indices.length; i++) {
			int indx = i + num_samples;
			double d = y_vector[indx] - fx[indx]; // fx[indx] == vector[i]
			double wd = d * weights[indx];
			s_rms += d * wd;
			wymfw[indx] =   wd;
		}
		double rms = Math.sqrt(s_rms); // assuming sum_weights == 1.0; /pure_weight); they should be re-normalized after adding regularization
		if (rms_fp != null) {
			rms_fp[0] = rms;
			rms_fp[1] = rms_pure;
		}
		return wymfw;
	}
	
	public static String getChecksum(Serializable object) throws IOException, NoSuchAlgorithmException {
	    ByteArrayOutputStream baos = null;
	    ObjectOutputStream oos = null;
	    try {
	        baos = new ByteArrayOutputStream();
	        oos = new ObjectOutputStream(baos);
	        oos.writeObject(object);
	        MessageDigest md = MessageDigest.getInstance("MD5");
	        byte[] thedigest = md.digest(baos.toByteArray());
	        return DatatypeConverter.printHexBinary(thedigest);
	    } finally {
	        oos.close();
	        baos.close();
	    }
	}
}
