Index: trunk/psModules/src/imcombine/pmSubtractionEquation.c
===================================================================
--- trunk/psModules/src/imcombine/pmSubtractionEquation.c	(revision 26035)
+++ trunk/psModules/src/imcombine/pmSubtractionEquation.c	(revision 26893)
@@ -17,5 +17,6 @@
 //#define TESTING                         // TESTING output for debugging; may not work with threads!
 
-#define USE_WEIGHT                      // Include weight (1/variance) in equation?
+//#define USE_WEIGHT                      // Include weight (1/variance) in equation?
+//#define USE_WINDOW                      // Include weight (1/variance) in equation?
 
 
@@ -27,11 +28,15 @@
 static bool calculateMatrixVector(psImage *matrix, // Least-squares matrix, updated
                                   psVector *vector, // Least-squares vector, updated
+                                  double *norm,     // Normalisation, updated
                                   const psKernel *input, // Input image (target)
                                   const psKernel *reference, // Reference image (convolution source)
                                   const psKernel *weight,  // Weight image
+                                  const psKernel *window,  // Window image
                                   const psArray *convolutions,         // Convolutions for each kernel
                                   const pmSubtractionKernels *kernels, // Kernels
                                   const psImage *polyValues, // Spatial polynomial values
-                                  int footprint // (Half-)Size of stamp
+                                  int footprint, // (Half-)Size of stamp
+                                  int normWindow, // Window (half-)size for normalisation measurement
+                                  const pmSubtractionEquationCalculationMode mode
                                   )
 {
@@ -51,4 +56,5 @@
 
     // Evaluate polynomial-polynomial terms
+    // XXX we can skip this if we are not calculating kernel coeffs
     for (int iyOrder = 0, iIndex = 0; iyOrder <= spatialOrder; iyOrder++) {
         for (int ixOrder = 0; ixOrder <= spatialOrder - iyOrder; ixOrder++, iIndex++) {
@@ -64,4 +70,18 @@
     }
 
+    // initialize the matrix and vector for NOP on all coeffs.  we only fill in the coeffs we
+    // choose to calculate
+    psImageInit(matrix, 0.0);
+    psVectorInit(vector, 1.0);
+    for (int i = 0; i < matrix->numCols; i++) {
+        matrix->data.F64[i][i] = 1.0;
+    }
+
+    // the order of the elements in the matrix and vector is:
+    // [kernel 0, x^0 y^0][kernel 1 x^0 y^0]...[kernel N, x^0 y^0]
+    // [kernel 0, x^1 y^0][kernel 1 x^1 y^0]...[kernel N, x^1 y^0]
+    // [kernel 0, x^n y^m][kernel 1 x^n y^m]...[kernel N, x^n y^m]
+    // normalization
+    // bg 0, bg 1, bg 2 (only 0 is currently used?)
 
     for (int i = 0; i < numKernels; i++) {
@@ -74,17 +94,22 @@
                 for (int x = - footprint; x <= footprint; x++) {
                     double cc = iConv->kernel[y][x] * jConv->kernel[y][x];
-#ifdef USE_WEIGHT
-                    cc *= weight->kernel[y][x];
-#endif
+                    if (weight) {
+                        cc *= weight->kernel[y][x];
+                    }
+                    if (window) {
+                        cc *= window->kernel[y][x];
+                    }
                     sumCC += cc;
                 }
             }
 
-            // Spatial variation
-            for (int iTerm = 0, iIndex = i; iTerm < numPoly; iTerm++, iIndex += numKernels) {
-                for (int jTerm = 0, jIndex = j; jTerm < numPoly; jTerm++, jIndex += numKernels) {
-                    double value = sumCC * poly2[iTerm][jTerm];
-                    matrix->data.F64[iIndex][jIndex] = value;
-                    matrix->data.F64[jIndex][iIndex] = value;
+            // Spatial variation of kernel coeffs
+            if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+                for (int iTerm = 0, iIndex = i; iTerm < numPoly; iTerm++, iIndex += numKernels) {
+                    for (int jTerm = 0, jIndex = j; jTerm < numPoly; jTerm++, jIndex += numKernels) {
+                        double value = sumCC * poly2[iTerm][jTerm];
+                        matrix->data.F64[iIndex][jIndex] = value;
+                        matrix->data.F64[jIndex][iIndex] = value;
+                    }
                 }
             }
@@ -102,10 +127,16 @@
                 double rc = ref * conv;
                 double c = conv;
-#ifdef USE_WEIGHT
-                float wtVal = weight->kernel[y][x];
-                ic *= wtVal;
-                rc *= wtVal;
-                c *= wtVal;
-#endif
+                if (weight) {
+                    float wtVal = weight->kernel[y][x];
+                    ic *= wtVal;
+                    rc *= wtVal;
+                    c *= wtVal;
+                }
+                if (window) {
+                    float winVal = window->kernel[y][x];
+                    ic *= winVal;
+                    rc *= winVal;
+                    c  *= winVal;
+                }
                 sumIC += ic;
                 sumRC += rc;
@@ -117,9 +148,22 @@
             double normTerm = sumRC * poly[iTerm];
             double bgTerm = sumC * poly[iTerm];
-            matrix->data.F64[iIndex][normIndex] = normTerm;
-            matrix->data.F64[normIndex][iIndex] = normTerm;
-            matrix->data.F64[iIndex][bgIndex] = bgTerm;
-            matrix->data.F64[bgIndex][iIndex] = bgTerm;
-            vector->data.F64[iIndex] = sumIC * poly[iTerm];
+            if ((mode & PM_SUBTRACTION_EQUATION_NORM) && (mode & PM_SUBTRACTION_EQUATION_KERNELS)) {
+                matrix->data.F64[iIndex][normIndex] = normTerm;
+                matrix->data.F64[normIndex][iIndex] = normTerm;
+            }
+            if ((mode & PM_SUBTRACTION_EQUATION_BG) && (mode & PM_SUBTRACTION_EQUATION_KERNELS)) {
+                matrix->data.F64[iIndex][bgIndex] = bgTerm;
+                matrix->data.F64[bgIndex][iIndex] = bgTerm;
+            }
+            if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+                vector->data.F64[iIndex] = sumIC * poly[iTerm];
+                if (!(mode & PM_SUBTRACTION_EQUATION_NORM)) {
+                    // subtract norm * sumRC * poly[iTerm]
+                    psAssert (kernels->solution1, "programming error: define solution first!");
+                    int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+                    double norm = fabs(kernels->solution1->data.F64[normIndex]);  // Normalisation
+                    vector->data.F64[iIndex] -= norm * normTerm;
+                }
+            }
         }
     }
@@ -130,4 +174,5 @@
     double sumR = 0.0;                  // Sum of the reference
     double sumI = 0.0;                  // Sum of the input
+    double normI1 = 0.0, normI2 = 0.0;  // Sum of I_1 and I_2 within the normalisation window
     for (int y = - footprint; y <= footprint; y++) {
         for (int x = - footprint; x <= footprint; x++) {
@@ -137,12 +182,26 @@
             double rr = PS_SQR(ref);
             double one = 1.0;
-#ifdef USE_WEIGHT
-            float wtVal = weight->kernel[y][x];
-            rr *= wtVal;
-            ir *= wtVal;
-            in *= wtVal;
-            ref *= wtVal;
-            one *= wtVal;
-#endif
+
+            if (PS_SQR(x) + PS_SQR(y) <= PS_SQR(normWindow)) {
+                normI1 += ref;
+                normI2 += in;
+            }
+
+            if (weight) {
+                float wtVal = weight->kernel[y][x];
+                rr *= wtVal;
+                ir *= wtVal;
+                in *= wtVal;
+                ref *= wtVal;
+                one *= wtVal;
+            }
+            if (window) {
+                float  winVal = window->kernel[y][x];
+                rr      *= winVal;
+                ir      *= winVal;
+                in      *= winVal;
+                ref *= winVal;
+                one *= winVal;
+            }
             sumRR += rr;
             sumIR += ir;
@@ -152,41 +211,58 @@
         }
     }
-    matrix->data.F64[normIndex][normIndex] = sumRR;
-    matrix->data.F64[bgIndex][bgIndex] = sum1;
-    matrix->data.F64[normIndex][bgIndex] = matrix->data.F64[bgIndex][normIndex] = sumR;
-    vector->data.F64[normIndex] = sumIR;
-    vector->data.F64[bgIndex] = sumI;
+
+    *norm = normI2 / normI1;
+
+    if (mode & PM_SUBTRACTION_EQUATION_NORM) {
+        matrix->data.F64[normIndex][normIndex] = sumRR;
+        vector->data.F64[normIndex] = sumIR;
+        // subtract sum over kernels * kernel solution
+    }
+    if (mode & PM_SUBTRACTION_EQUATION_BG) {
+        matrix->data.F64[bgIndex][bgIndex] = sum1;
+        vector->data.F64[bgIndex] = sumI;
+    }
+    if ((mode & PM_SUBTRACTION_EQUATION_NORM) && (mode & PM_SUBTRACTION_EQUATION_BG)) {
+        matrix->data.F64[normIndex][bgIndex] = sumR;
+        matrix->data.F64[bgIndex][normIndex] = sumR;
+    }
+
+    // check for any NAN values in the result, skip if found:
+    for (int iy = 0; iy < matrix->numRows; iy++) {
+        for (int ix = 0; ix < matrix->numCols; ix++) {
+            if (!isfinite(matrix->data.F64[iy][ix])) {
+                fprintf (stderr, "WARNING: NAN in matrix\n");
+                return false;
+            }
+        }
+    }
+    for (int ix = 0; ix < vector->n; ix++) {
+        if (!isfinite(vector->data.F64[ix])) {
+            fprintf (stderr, "WARNING: NAN in vector\n");
+            return false;
+        }
+    }
 
     return true;
 }
 
+
 // Calculate the least-squares matrix and vector for dual convolution
-static bool calculateDualMatrixVector(psImage *matrix1, // Least-squares matrix, updated
-                                      psVector *vector1, // Least-squares vector, updated
-                                      psImage *matrix2,  // Least-squares matrix, updated
-                                      psVector *vector2, // Least-squares vector, updated
-                                      psImage *matrixX,  // Cross-matrix
+static bool calculateDualMatrixVector(psImage *matrix, // Least-squares matrix, updated
+                                      psVector *vector, // Least-squares vector, updated
+                                      double *norm,     // Normalisation, updated
                                       const psKernel *image1, // Image 1
                                       const psKernel *image2, // Image 2
                                       const psKernel *weight,  // Weight image
+                                      const psKernel *window,  // Window image
                                       const psArray *convolutions1, // Convolutions of image 1 for each kernel
                                       const psArray *convolutions2, // Convolutions of image 2 for each kernel
                                       const pmSubtractionKernels *kernels, // Kernels
                                       const psImage *polyValues, // Spatial polynomial values
-                                      int footprint // (Half-)Size of stamp
+                                      int footprint, // (Half-)Size of stamp
+                                      int normWindow, // Window (half-)size for normalisation measurement
+                                      const pmSubtractionEquationCalculationMode mode
                                       )
 {
-    // A_ij = A_i A_j
-    // B_ij = B_i B_j
-    // C_ij = A_i B_j
-    // d_i = A_i I_2
-    // e_i = B_i I_2
-
-    // A_i = I_1 * k_i
-    // B_i = I_2 * k_i
-
-    // Background: A_i = 1.0
-    // Normalisation: A_i = I_1
-
     int numKernels = kernels->num;                      // Number of kernels
     int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
@@ -196,4 +272,19 @@
     double poly[numPoly];                                 // Polynomial terms
     double poly2[numPoly][numPoly];                       // Polynomial-polynomial values
+
+    int numBackground = PM_SUBTRACTION_POLYTERMS(kernels->bgOrder); // Number of background terms
+    int numParams = numKernels * numPoly + 1 + numBackground;       // Number of regular parameters
+    int numParams2 = numKernels * numPoly;                          // Number of additional parameters for dual
+    int numDual = numParams + numParams2;                           // Total number of parameters for dual
+
+    psAssert(matrix &&
+             matrix->type.type == PS_TYPE_F64 &&
+             matrix->numCols == numDual &&
+             matrix->numRows == numDual,
+             "Least-squares matrix is bad.");
+    psAssert(vector &&
+             vector->type.type == PS_TYPE_F64 &&
+             vector->n == numDual,
+             "Least-squares vector is bad.");
 
     // Evaluate polynomial-polynomial terms
@@ -212,4 +303,12 @@
 
 
+    // initialize the matrix and vector for NOP on all coeffs.  we only fill in the coeffs we
+    // choose to calculate
+    psImageInit(matrix, 0.0);
+    psVectorInit(vector, 1.0);
+    for (int i = 0; i < matrix->numCols; i++) {
+        matrix->data.F64[i][i] = 1.0;
+    }
+
     for (int i = 0; i < numKernels; i++) {
         psKernel *iConv1 = convolutions1->data[i]; // Convolution 1 for index i
@@ -219,7 +318,7 @@
             psKernel *jConv2 = convolutions2->data[j]; // Convolution 2 for index j
 
-            double sumAA = 0.0;         // Sum of convolution products for matrix A
-            double sumBB = 0.0;         // Sum of convolution products for matrix B
-            double sumAB = 0.0;         // Sum of convolution products for matrix C
+            double sumAA = 0.0;         // Sum of convolution products between image 1
+            double sumBB = 0.0;         // Sum of convolution products between image 2
+            double sumAB = 0.0;         // Sum of convolution products across images 1 and 2
             for (int y = - footprint; y <= footprint; y++) {
                 for (int x = - footprint; x <= footprint; x++) {
@@ -227,10 +326,16 @@
                     double bb = iConv2->kernel[y][x] * jConv2->kernel[y][x];
                     double ab = iConv1->kernel[y][x] * jConv2->kernel[y][x];
-#ifdef USE_WEIGHT
-                    float wtVal = weight->kernel[y][x];
-                    aa *= wtVal;
-                    bb *= wtVal;
-                    ab *= wtVal;
-#endif
+                    if (weight) {
+                        float wtVal = weight->kernel[y][x];
+                        aa *= wtVal;
+                        bb *= wtVal;
+                        ab *= wtVal;
+                    }
+                    if (window) {
+                        float wtVal = window->kernel[y][x];
+                        aa *= wtVal;
+                        bb *= wtVal;
+                        ab *= wtVal;
+                    }
                     sumAA += aa;
                     sumBB += bb;
@@ -239,15 +344,21 @@
             }
 
-            // Spatial variation
-            for (int iTerm = 0, iIndex = i; iTerm < numPoly; iTerm++, iIndex += numKernels) {
-                for (int jTerm = 0, jIndex = j; jTerm < numPoly; jTerm++, jIndex += numKernels) {
-                    double aa = sumAA * poly2[iTerm][jTerm];
-                    double bb = sumBB * poly2[iTerm][jTerm];
-                    double ab = sumAB * poly2[iTerm][jTerm];
-                    matrix1->data.F64[iIndex][jIndex] = aa;
-                    matrix1->data.F64[jIndex][iIndex] = aa;
-                    matrix2->data.F64[iIndex][jIndex] = bb;
-                    matrix2->data.F64[jIndex][iIndex] = bb;
-                    matrixX->data.F64[iIndex][jIndex] = ab;
+            // Spatial variation of kernel coeffs
+            if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+                for (int iTerm = 0, iIndex = i; iTerm < numPoly; iTerm++, iIndex += numKernels) {
+                    for (int jTerm = 0, jIndex = j; jTerm < numPoly; jTerm++, jIndex += numKernels) {
+                        double aa = sumAA * poly2[iTerm][jTerm];
+                        double bb = sumBB * poly2[iTerm][jTerm];
+                        double ab = sumAB * poly2[iTerm][jTerm];
+
+                        matrix->data.F64[iIndex][jIndex] = aa;
+                        matrix->data.F64[jIndex][iIndex] = aa;
+
+                        matrix->data.F64[iIndex + numParams][jIndex + numParams] = bb;
+                        matrix->data.F64[jIndex + numParams][iIndex + numParams] = bb;
+
+                        matrix->data.F64[iIndex][jIndex + numParams] = ab;
+                        matrix->data.F64[jIndex + numParams][iIndex] = ab;
+                    }
                 }
             }
@@ -259,32 +370,37 @@
                 for (int x = - footprint; x <= footprint; x++) {
                     double ab = iConv1->kernel[y][x] * jConv2->kernel[y][x];
-#ifdef USE_WEIGHT
-                    ab *= weight->kernel[y][x];
-#endif
+                    if (weight) {
+                        ab *= weight->kernel[y][x];
+                    }
+                    if (window) {
+                        ab *= window->kernel[y][x];
+                    }
                     sumAB += ab;
                 }
             }
 
-            // Spatial variation
-            for (int iTerm = 0, iIndex = i; iTerm < numPoly; iTerm++, iIndex += numKernels) {
-                for (int jTerm = 0, jIndex = j; jTerm < numPoly; jTerm++, jIndex += numKernels) {
-                    double ab = sumAB * poly2[iTerm][jTerm];
-                    matrixX->data.F64[iIndex][jIndex] = ab;
-                }
-            }
-        }
-
-        double sumAI2 = 0.0;            // Sum of A.I_2 products (for vector 1)
-        double sumBI2 = 0.0;            // Sum of B.I_2 products (for vector 2)
-        double sumAI1 = 0.0;            // Sum of A.I_1 products (for matrix 1, normalisation)
-        double sumA = 0.0;              // Sum of A (for matrix 1, background)
-        double sumBI1 = 0.0;            // Sum of B.I_1 products (for matrix X, normalisation)
-        double sumB = 0.0;              // Sum of B products (for matrix X, background)
-        double sumI2 = 0.0;             // Sum of I_2 (for vector 1, background)
-        double sumI1I2 = 0.0;           // Sum of I_1.I_2 (for vector 1, normalisation)
+            // Spatial variation of kernel coeffs
+            if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+                for (int iTerm = 0, iIndex = i; iTerm < numPoly; iTerm++, iIndex += numKernels) {
+                    for (int jTerm = 0, jIndex = j; jTerm < numPoly; jTerm++, jIndex += numKernels) {
+                        double ab = sumAB * poly2[iTerm][jTerm];
+                        matrix->data.F64[iIndex][jIndex + numParams] = ab;
+                        matrix->data.F64[jIndex + numParams][iIndex] = ab;
+                    }
+                }
+            }
+        }
+
+        double sumAI2 = 0.0;            // Sum of A.I_2 products (for vector)
+        double sumBI2 = 0.0;            // Sum of B.I_2 products (for vector)
+        double sumAI1 = 0.0;            // Sum of A.I_1 products (for matrix, normalisation)
+        double sumA = 0.0;              // Sum of A (for matrix, background)
+        double sumBI1 = 0.0;            // Sum of B.I_1 products (for matrix, normalisation)
+        double sumB = 0.0;              // Sum of B products (for matrix, background)
+        double sumI2 = 0.0;             // Sum of I_2 (for vector, background)
         for (int y = - footprint; y <= footprint; y++) {
             for (int x = - footprint; x <= footprint; x++) {
-                float a = iConv1->kernel[y][x];
-                float b = iConv2->kernel[y][x];
+                double a = iConv1->kernel[y][x];
+                double b = iConv2->kernel[y][x];
                 float i1 = image1->kernel[y][x];
                 float i2 = image2->kernel[y][x];
@@ -294,18 +410,25 @@
                 double ai1 = a * i1;
                 double bi1 = b * i1;
-                double i1i2 = i1 * i2;
-
-#ifdef USE_WEIGHT
-                float wtVal = weight->kernel[y][x];
-                ai2 *= wtVal;
-                bi2 *= wtVal;
-                ai1 *= wtVal;
-                bi1 *= wtVal;
-                i1i2 *= wtVal;
-                a *= wtVal;
-                b *= wtVal;
-                i2 *= wtVal;
-#endif
-
+
+                if (weight) {
+                    float wtVal = weight->kernel[y][x];
+                    ai2 *= wtVal;
+                    bi2 *= wtVal;
+                    ai1 *= wtVal;
+                    bi1 *= wtVal;
+                    a *= wtVal;
+                    b *= wtVal;
+                    i2 *= wtVal;
+                }
+                if (window) {
+                    float wtVal = window->kernel[y][x];
+                    ai2 *= wtVal;
+                    bi2 *= wtVal;
+                    ai1 *= wtVal;
+                    bi1 *= wtVal;
+                    a *= wtVal;
+                    b *= wtVal;
+                    i2 *= wtVal;
+                }
                 sumAI2 += ai2;
                 sumBI2 += bi2;
@@ -315,5 +438,4 @@
                 sumB += b;
                 sumI2 += i2;
-                sumI1I2 += i1i2;
             }
         }
@@ -323,28 +445,45 @@
             double bi2 = sumBI2 * poly[iTerm];
             double ai1 = sumAI1 * poly[iTerm];
-            double a = sumA * poly[iTerm];
+            double a   = sumA * poly[iTerm];
             double bi1 = sumBI1 * poly[iTerm];
-            double b = sumB * poly[iTerm];
-
-            matrix1->data.F64[iIndex][normIndex] = ai1;
-            matrix1->data.F64[normIndex][iIndex] = ai1;
-            matrix1->data.F64[iIndex][bgIndex] = a;
-            matrix1->data.F64[bgIndex][iIndex] = a;
-            vector1->data.F64[iIndex] = ai2;
-            vector2->data.F64[iIndex] = bi2;
-            matrixX->data.F64[iIndex][normIndex] = bi1;
-            matrixX->data.F64[iIndex][bgIndex] = b;
-        }
-    }
-
-    double sumI1 = 0.0;                 // Sum of I_1 (for matrix 1, background-normalisation)
-    double sumI1I1 = 0.0;               // Sum of I_1^2 (for matrix 1, normalisation-normalisation)
-    double sum1 = 0.0;                  // Sum of 1 (for matrix 1, background-background)
-    double sumI2 = 0.0;                 // Sum of I_2 (for vector 1, background)
-    double sumI1I2 = 0.0;               // Sum of I_1.I_2 (for vector 1, normalisation)
+            double b   = sumB * poly[iTerm];
+
+            if ((mode & PM_SUBTRACTION_EQUATION_NORM) && (mode & PM_SUBTRACTION_EQUATION_KERNELS)) {
+                matrix->data.F64[iIndex][normIndex] = ai1;
+                matrix->data.F64[normIndex][iIndex] = ai1;
+                matrix->data.F64[iIndex + numParams][normIndex] = bi1;
+                matrix->data.F64[normIndex][iIndex + numParams] = bi1;
+            }
+            if ((mode & PM_SUBTRACTION_EQUATION_BG) && (mode & PM_SUBTRACTION_EQUATION_KERNELS)) {
+                matrix->data.F64[iIndex][bgIndex] = a;
+                matrix->data.F64[bgIndex][iIndex] = a;
+                matrix->data.F64[iIndex + numParams][bgIndex] = b;
+                matrix->data.F64[bgIndex][iIndex + numParams] = b;
+            }
+            if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+                vector->data.F64[iIndex] = ai2;
+                vector->data.F64[iIndex + numParams] = bi2;
+                if (!(mode & PM_SUBTRACTION_EQUATION_NORM)) {
+                    // subtract norm * sumRC * poly[iTerm]
+                    psAssert (kernels->solution1, "programming error: define solution first!");
+                    int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+                    double norm = fabs(kernels->solution1->data.F64[normIndex]);  // Normalisation
+                    vector->data.F64[iIndex] -= norm * ai1;
+                    vector->data.F64[iIndex + numParams] -= norm * bi1;
+                }
+            }
+        }
+    }
+
+    double sumI1 = 0.0;                 // Sum of I_1 (for matrix, background-normalisation)
+    double sumI1I1 = 0.0;               // Sum of I_1^2 (for matrix, normalisation-normalisation)
+    double sum1 = 0.0;                  // Sum of 1 (for matrix, background-background)
+    double sumI2 = 0.0;                 // Sum of I_2 (for vector, background)
+    double sumI1I2 = 0.0;               // Sum of I_1.I_2 (for vector, normalisation)
+    double normI1 = 0.0, normI2 = 0.0;  // Sum of I_1 and I_2 within the normalisation window
     for (int y = - footprint; y <= footprint; y++) {
         for (int x = - footprint; x <= footprint; x++) {
-            float i1 = image1->kernel[y][x];
-            float i2 = image2->kernel[y][x];
+            double i1 = image1->kernel[y][x];
+            double i2 = image2->kernel[y][x];
 
             double i1i1 = i1 * i1;
@@ -352,13 +491,25 @@
             double i1i2 = i1 * i2;
 
-#ifdef USE_WEIGHT
-            float wtVal = weight->kernel[y][x];
-            i1 *= wtVal;
-            i1i1 *= wtVal;
-            one *= wtVal;
-            i2 *= wtVal;
-            i1i2 *= wtVal;
-#endif
-
+            if (PS_SQR(x) + PS_SQR(y) <= PS_SQR(normWindow)) {
+                normI1 += i1;
+                normI2 += i2;
+            }
+
+            if (weight) {
+                float wtVal = weight->kernel[y][x];
+                i1 *= wtVal;
+                i1i1 *= wtVal;
+                one *= wtVal;
+                i2 *= wtVal;
+                i1i2 *= wtVal;
+            }
+            if (window) {
+                float wtVal = window->kernel[y][x];
+                i1 *= wtVal;
+                i1i1 *= wtVal;
+                one *= wtVal;
+                i2 *= wtVal;
+                i1i2 *= wtVal;
+            }
             sumI1 += i1;
             sumI1I1 += i1i1;
@@ -368,80 +519,43 @@
         }
     }
-    matrix1->data.F64[bgIndex][normIndex] = sumI1;
-    matrix1->data.F64[normIndex][bgIndex] = sumI1;
-    matrix1->data.F64[normIndex][normIndex] = sumI1I1;
-    matrix1->data.F64[bgIndex][bgIndex] = sum1;
-    vector1->data.F64[bgIndex] = sumI2;
-    vector1->data.F64[normIndex] = sumI1I2;
+
+    *norm = normI2 / normI1;
+
+    if (mode & PM_SUBTRACTION_EQUATION_NORM) {
+        matrix->data.F64[normIndex][normIndex] = sumI1I1;
+        vector->data.F64[normIndex] = sumI1I2;
+    }
+    if (mode & PM_SUBTRACTION_EQUATION_BG) {
+        matrix->data.F64[bgIndex][bgIndex] = sum1;
+        vector->data.F64[bgIndex] = sumI2;
+    }
+    if ((mode & PM_SUBTRACTION_EQUATION_NORM) && (mode & PM_SUBTRACTION_EQUATION_BG)) {
+        matrix->data.F64[bgIndex][normIndex] = sumI1;
+        matrix->data.F64[normIndex][bgIndex] = sumI1;
+    }
+
+    // check for any NAN values in the result, skip if found:
+    for (int iy = 0; iy < matrix->numRows; iy++) {
+        for (int ix = 0; ix < matrix->numCols; ix++) {
+            if (!isfinite(matrix->data.F64[iy][ix])) {
+                fprintf (stderr, "WARNING: NAN in matrix\n");
+                return false;
+            }
+        }
+    }
+    for (int ix = 0; ix < vector->n; ix++) {
+        if (!isfinite(vector->data.F64[ix])) {
+            fprintf (stderr, "WARNING: NAN in vector\n");
+            return false;
+        }
+    }
+
 
     return true;
 }
 
-// Merge dual matrices and vectors into single matrix equation
-// Have: Aa = Ct.b + d
-// Have: Ca = Bb + e
-// Set: F = ( A -Ct ;  C -B )
-// Set: g = ( a ; b )
-// Set: h = ( d ; e )
-// So that we combine the above two equations: Fg = h
-static bool calculateEquationDual(psImage **outMatrix,
-                                  psVector **outVector,
-                                  const psImage *sumMatrix1,
-                                  const psImage *sumMatrix2,
-                                  const psImage *sumMatrixX,
-                                  const psVector *sumVector1,
-                                  const psVector *sumVector2
-                                  )
-{
-    psAssert(sumMatrix1 && sumMatrix2 && sumMatrixX, "Require input matrices");
-    psAssert(sumVector1 && sumVector2, "Require input vectors");
-    int num1 = sumVector1->n;   // Number of parameters in first set
-    int num2 = sumVector2->n;   // Number of parameters in second set
-    int num = num1 + num2;      // Number of parameters in new set
-
-    psAssert(sumMatrix1->type.type == PS_TYPE_F64 &&
-             sumMatrix2->type.type == PS_TYPE_F64 &&
-             sumMatrixX->type.type == PS_TYPE_F64 &&
-             sumVector1->type.type == PS_TYPE_F64 &&
-             sumVector2->type.type == PS_TYPE_F64,
-             "Require input type is F64");
-
-    psAssert(outMatrix, "Require output matrix");
-    psAssert(outVector, "Require output vector");
-    if (!*outMatrix) {
-        *outMatrix = psImageAlloc(num, num, PS_TYPE_F64);
-    }
-    if (!*outVector) {
-        *outVector = psVectorAlloc(num, PS_TYPE_F64);
-    }
-    psImage *matrix = *outMatrix;
-    psVector *vector = *outVector;
-
-    psAssert(sumMatrix1->numCols == num1 && sumMatrix1->numRows == num1, "Require size NxN");
-    psAssert(sumMatrix2->numCols == num2 && sumMatrix2->numRows == num2, "Require size MxM");
-    psAssert(sumMatrixX->numCols == num1 && sumMatrixX->numRows == num2, "Require size MxN");
-
-    memcpy(vector->data.F64, sumVector1->data.F64, num1 * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
-    memcpy(&vector->data.F64[num1], sumVector2->data.F64, num2 * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
-
-    for (int i = 0; i < num1; i++) {
-        memcpy(matrix->data.F64[i], sumMatrix1->data.F64[i], num1 * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
-        for (int j = 0, k = num1; j < num2; j++, k++) {
-            matrix->data.F64[i][k] = - sumMatrixX->data.F64[j][i];
-        }
-    }
-    for (int i1 = 0, i2 = num1; i1 < num2; i1++, i2++) {
-        memcpy(matrix->data.F64[i2], sumMatrixX->data.F64[i1], num1 * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
-        for (int j = 0, k = num1; j < num2; j++, k++) {
-            matrix->data.F64[i2][k] = - sumMatrix2->data.F64[i1][j];
-        }
-    }
-
-    return true;
-}
-
-
+#if 1
 // Add in penalty term to least-squares vector
-static bool calculatePenalty(psImage *matrix,                     // Matrix to which to add in penalty term
+bool calculatePenalty(psImage *matrix,                     // Matrix to which to add in penalty term
                              psVector *vector,                    // Vector to which to add in penalty term
                              const pmSubtractionKernels *kernels, // Kernel parameters
@@ -456,9 +570,29 @@
     int spatialOrder = kernels->spatialOrder; // Order of spatial variations
     int numKernels = kernels->num; // Number of kernel components
+    int numSpatial = PM_SUBTRACTION_POLYTERMS(spatialOrder); // Number of spatial variations
+    int numParams = numKernels * numSpatial;                 // Number of kernel parameters
+
+    // order is :
+    // [p_0,x_0,y_0 p_1,x_0,y_0, p_2,x_0,y_0]
+    // [p_0,x_1,y_0 p_1,x_1,y_0, p_2,x_1,y_0]
+    // [p_0,x_0,y_1 p_1,x_0,y_1, p_2,x_0,y_1]
+    // [norm]
+    // [bg]
+    // [q_0,x_0,y_0 q_1,x_0,y_0, q_2,x_0,y_0]
+    // [q_0,x_1,y_0 q_1,x_1,y_0, q_2,x_1,y_0]
+    // [q_0,x_0,y_1 q_1,x_0,y_1, q_2,x_0,y_1]
+
     for (int i = 0; i < numKernels; i++) {
         for (int yOrder = 0, index = i; yOrder <= spatialOrder; yOrder++) {
             for (int xOrder = 0; xOrder <= spatialOrder - yOrder; xOrder++, index += numKernels) {
                 // Contribution to chi^2: a_i^2 P_i
-                matrix->data.F64[index][index] -= norm * penalties->data.F32[i];
+                psAssert(isfinite(penalties->data.F32[i]), "Invalid penalty");
+                matrix->data.F64[index][index] += norm * penalties->data.F32[i];
+                if (kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+                    matrix->data.F64[index + numParams + 2][index + numParams + 2] += norm * penalties->data.F32[i];
+                    // matrix[i][i] is ~ (k_i * I_1)(k_i * I_1)
+                    // penalties scale with second moments
+                    //
+                }
             }
         }
@@ -467,4 +601,5 @@
     return true;
 }
+# endif
 
 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -476,9 +611,9 @@
 // Calculate the value of a polynomial, specified by coefficients and polynomial values
 double p_pmSubtractionCalculatePolynomial(const psVector *coeff, // Coefficients
-                                                 const psImage *polyValues, // Polynomial values
-                                                 int order, // Order of polynomials
-                                                 int index, // Index at which to begin
-                                                 int step // Step between subsequent indices
-                                                 )
+                                          const psImage *polyValues, // Polynomial values
+                                          int order, // Order of polynomials
+                                          int index, // Index at which to begin
+                                          int step // Step between subsequent indices
+                                          )
 {
     double sum = 0.0;                   // Value of the polynomial sum
@@ -495,5 +630,5 @@
 
 double p_pmSubtractionSolutionCoeff(const pmSubtractionKernels *kernels, const psImage *polyValues,
-                                           int index, bool wantDual)
+                                    int index, bool wantDual)
 {
 #if 0
@@ -548,10 +683,11 @@
     const pmSubtractionKernels *kernels = job->args->data[1]; // Kernels
     int index = PS_SCALAR_VALUE(job->args->data[2], S32); // Stamp index
-
-    return pmSubtractionCalculateEquationStamp(stamps, kernels, index);
+    pmSubtractionEquationCalculationMode mode  = PS_SCALAR_VALUE(job->args->data[3], S32); // calculation model
+
+    return pmSubtractionCalculateEquationStamp(stamps, kernels, index, mode);
 }
 
 bool pmSubtractionCalculateEquationStamp(pmSubtractionStampList *stamps, const pmSubtractionKernels *kernels,
-                                         int index)
+                                         int index, const pmSubtractionEquationCalculationMode mode)
 {
     PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, false);
@@ -566,12 +702,19 @@
     int numBackground = PM_SUBTRACTION_POLYTERMS(kernels->bgOrder); // Number of background terms
 
+    // numKernels is the number of unique kernel images (one for each Gaussian modified by a specific polynomial).
+    // = \sum_i^N_Gaussians [(order + 1) * (order + 2) / 2], eg for 1 Gauss and 1st order, numKernels = 3
+
     // Total number of parameters to solve for: coefficient of each kernel basis function, multipled by the
     // number of coefficients for the spatial polynomial, normalisation and a constant background offset.
     int numParams = numKernels * numSpatial + 1 + numBackground;
+    if (kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+        // An additional image is convolved
+        numParams += numKernels * numSpatial;
+    }
 
     pmSubtractionStamp *stamp = stamps->stamps->data[index]; // Stamp of interest
     psAssert(stamp->status == PM_SUBTRACTION_STAMP_CALCULATE, "We only operate on stamps with this state.");
 
-    // Generate convolutions
+    // Generate convolutions: these are generated once and saved
     if (!pmSubtractionConvolveStamp(stamp, kernels, footprint)) {
         psError(PS_ERR_UNKNOWN, false, "Unable to convolve stamp %d.", index);
@@ -603,44 +746,47 @@
 #endif
 
+    // XXX visualize the set of convolved stamps
+
     psImage *polyValues = p_pmSubtractionPolynomial(NULL, spatialOrder,
                                                     stamp->xNorm, stamp->yNorm); // Polynomial terms
 
-    bool new = stamp->vector1 ? false : true; // Is this a new run?
+    bool new = stamp->vector ? false : true; // Is this a new run?
     if (new) {
-        stamp->matrix1 = psImageAlloc(numParams, numParams, PS_TYPE_F64);
-        stamp->vector1 = psVectorAlloc(numParams, PS_TYPE_F64);
+        stamp->matrix = psImageAlloc(numParams, numParams, PS_TYPE_F64);
+        stamp->vector = psVectorAlloc(numParams, PS_TYPE_F64);
     }
 #ifdef TESTING
-    psImageInit(stamp->matrix1, NAN);
-    psVectorInit(stamp->vector1, NAN);
+    psImageInit(stamp->matrix, NAN);
+    psVectorInit(stamp->vector, NAN);
 #endif
 
     bool status;                    // Status of least-squares matrix/vector calculation
+
+    psKernel *weight = NULL;
+    psKernel *window = NULL;
+
+#ifdef USE_WEIGHT
+    weight = stamp->weight;
+#endif
+#ifdef USE_WINDOW
+    window = stamps->window;
+#endif
+
     switch (kernels->mode) {
       case PM_SUBTRACTION_MODE_1:
-        status = calculateMatrixVector(stamp->matrix1, stamp->vector1, stamp->image2, stamp->image1,
-                                       stamp->weight, stamp->convolutions1, kernels, polyValues,
-                                       footprint);
+        status = calculateMatrixVector(stamp->matrix, stamp->vector, &stamp->norm, stamp->image2, stamp->image1,
+                                       weight, window, stamp->convolutions1, kernels,
+                                       polyValues, footprint, stamps->normWindow, mode);
         break;
       case PM_SUBTRACTION_MODE_2:
-        status = calculateMatrixVector(stamp->matrix1, stamp->vector1, stamp->image1, stamp->image2,
-                                       stamp->weight, stamp->convolutions2, kernels, polyValues,
-                                       footprint);
+        status = calculateMatrixVector(stamp->matrix, stamp->vector, &stamp->norm, stamp->image1, stamp->image2,
+                                       weight, window, stamp->convolutions2, kernels,
+                                       polyValues, footprint, stamps->normWindow, mode);
         break;
       case PM_SUBTRACTION_MODE_DUAL:
-        if (new) {
-            stamp->matrix2 = psImageAlloc(numKernels * numSpatial, numKernels * numSpatial, PS_TYPE_F64);
-            stamp->matrixX = psImageAlloc(numParams, numKernels * numSpatial, PS_TYPE_F64);
-            stamp->vector2 = psVectorAlloc(numKernels * numSpatial, PS_TYPE_F64);
-        }
-#ifdef TESTING
-        psImageInit(stamp->matrix2, NAN);
-        psImageInit(stamp->matrixX, NAN);
-        psVectorInit(stamp->vector2, NAN);
-#endif
-        status = calculateDualMatrixVector(stamp->matrix1, stamp->vector1, stamp->matrix2, stamp->vector2,
-                                           stamp->matrixX, stamp->image1, stamp->image2, stamp->weight,
-                                           stamp->convolutions1, stamp->convolutions2, kernels, polyValues,
-                                           footprint);
+        status = calculateDualMatrixVector(stamp->matrix, stamp->vector, &stamp->norm,
+                                           stamp->image1, stamp->image2,
+                                           weight, window, stamp->convolutions1, stamp->convolutions2,
+                                           kernels, polyValues, footprint, stamps->normWindow, mode);
         break;
       default:
@@ -651,5 +797,5 @@
         stamp->status = PM_SUBTRACTION_STAMP_REJECTED;
         psWarning("Rejecting stamp %d (%d,%d) because of bad equation",
-                  index, (int)(stamp->x + 0.5), (int)(stamp->y + 0.5));
+                  index, (int)(stamp->x - 0.5), (int)(stamp->y - 0.5));
     } else {
         stamp->status = PM_SUBTRACTION_STAMP_USED;
@@ -659,15 +805,15 @@
     {
         psString matrixName = NULL;
-        psStringAppend(&matrixName, "matrix1_%d.fits", index);
+        psStringAppend(&matrixName, "matrix_%d.fits", index);
         psFits *matrixFile = psFitsOpen(matrixName, "w");
         psFree(matrixName);
-        psFitsWriteImage(matrixFile, NULL, stamp->matrix1, 0, NULL);
+        psFitsWriteImage(matrixFile, NULL, stamp->matrix, 0, NULL);
         psFitsClose(matrixFile);
 
         matrixName = NULL;
-        psStringAppend(&matrixName, "vector1_%d.fits", index);
-        psImage *dummy = psImageAlloc(stamp->vector1->n, 1, PS_TYPE_F64);
-        memcpy(dummy->data.F64[0], stamp->vector1->data.F64,
-               PSELEMTYPE_SIZEOF(PS_TYPE_F64) * stamp->vector1->n);
+        psStringAppend(&matrixName, "vector_%d.fits", index);
+        psImage *dummy = psImageAlloc(stamp->vector->n, 1, PS_TYPE_F64);
+        memcpy(dummy->data.F64[0], stamp->vector->data.F64,
+               PSELEMTYPE_SIZEOF(PS_TYPE_F64) * stamp->vector->n);
         matrixFile = psFitsOpen(matrixName, "w");
         psFree(matrixName);
@@ -675,33 +821,4 @@
         psFree(dummy);
         psFitsClose(matrixFile);
-
-        if (stamp->vector2) {
-            matrixName = NULL;
-            psStringAppend(&matrixName, "vector2_%d.fits", index);
-            dummy = psImageAlloc(stamp->vector2->n, 1, PS_TYPE_F64);
-            memcpy(dummy->data.F64[0], stamp->vector2->data.F64,
-                   PSELEMTYPE_SIZEOF(PS_TYPE_F64) * stamp->vector2->n);
-            matrixFile = psFitsOpen(matrixName, "w");
-            psFree(matrixName);
-            psFitsWriteImage(matrixFile, NULL, dummy, 0, NULL);
-            psFree(dummy);
-            psFitsClose(matrixFile);
-        }
-
-        if (kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
-            matrixName = NULL;
-            psStringAppend(&matrixName, "matrix2_%d.fits", index);
-            matrixFile = psFitsOpen(matrixName, "w");
-            psFree(matrixName);
-            psFitsWriteImage(matrixFile, NULL, stamp->matrix2, 0, NULL);
-            psFitsClose(matrixFile);
-
-            matrixName = NULL;
-            psStringAppend(&matrixName, "matrixX_%d.fits", index);
-            matrixFile = psFitsOpen(matrixName, "w");
-            psFree(matrixName);
-            psFitsWriteImage(matrixFile, NULL, stamp->matrixX, 0, NULL);
-            psFitsClose(matrixFile);
-        }
     }
 #endif
@@ -712,5 +829,6 @@
 }
 
-bool pmSubtractionCalculateEquation(pmSubtractionStampList *stamps, const pmSubtractionKernels *kernels)
+bool pmSubtractionCalculateEquation(pmSubtractionStampList *stamps, const pmSubtractionKernels *kernels,
+                                    const pmSubtractionEquationCalculationMode mode)
 {
     PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, false);
@@ -727,4 +845,11 @@
         }
 
+        if ((stamp->x <= 0.0) && (stamp->y <= 0.0)) {
+            psAbort ("bad stamp");
+        }
+        if (!isfinite(stamp->x) && !isfinite(stamp->y)) {
+            psAbort ("bad stamp");
+        }
+
         if (pmSubtractionThreaded()) {
             psThreadJob *job = psThreadJobAlloc("PSMODULES_SUBTRACTION_CALCULATE_EQUATION");
@@ -732,4 +857,5 @@
             psArrayAdd(job->args, 1, (pmSubtractionKernels*)kernels); // Casting away const to put on array
             PS_ARRAY_ADD_SCALAR(job->args, i, PS_TYPE_S32);
+            PS_ARRAY_ADD_SCALAR(job->args, mode, PS_TYPE_S32);
             if (!psThreadJobAddPending(job)) {
                 psFree(job);
@@ -738,5 +864,5 @@
             psFree(job);
         } else {
-            pmSubtractionCalculateEquationStamp(stamps, kernels, i);
+            pmSubtractionCalculateEquationStamp(stamps, kernels, i, mode);
         }
     }
@@ -748,4 +874,6 @@
 
     pmSubtractionVisualPlotLeastSquares(stamps);
+    pmSubtractionVisualShowKernels((pmSubtractionKernels  *)kernels);
+    pmSubtractionVisualShowBasis(stamps);
 
     psLogMsg("psModules.imcombine", PS_LOG_INFO, "Calculate equation: %f sec",
@@ -756,5 +884,28 @@
 }
 
-bool pmSubtractionSolveEquation(pmSubtractionKernels *kernels, const pmSubtractionStampList *stamps)
+// private functions used on pmSubtractionSolveEquation
+bool psVectorWriteFile (char *filename, const psVector *vector);
+bool psFitsWriteImageSimple (char *filename, psImage *image, psMetadata *header);
+
+psImage *p_pmSubSolve_wUt (psVector *w, psImage *U);
+psImage *p_pmSubSolve_VwUt (psImage *V, psImage *wUt);
+
+bool p_pmSubSolve_SetWeights (psVector *wApply, psVector *w, psVector *wMask);
+
+bool p_pmSubSolve_UtB (psVector **UtB, psImage *U, psVector *B);
+bool p_pmSubSolve_wUtB (psVector **wUtB, psVector *w, psVector *UtB);
+bool p_pmSubSolve_VwUtB (psVector **VwUtB, psImage *V, psVector *wUtB);
+
+bool p_pmSubSolve_Ax (psVector **B, psImage *A, psVector *x);
+bool p_pmSubSolve_VdV (double *value, psVector *x, psVector *y);
+bool p_pmSubSolve_y2 (double *y2, pmSubtractionKernels *kernels, const pmSubtractionStampList *stamps);
+
+psImage *p_pmSubSolve_Xvar (psImage *V, psVector *w);
+
+double p_pmSubSolve_ChiSquare (pmSubtractionKernels *kernels, const pmSubtractionStampList *stamps);
+
+bool pmSubtractionSolveEquation(pmSubtractionKernels *kernels,
+                                const pmSubtractionStampList *stamps,
+                                const pmSubtractionEquationCalculationMode mode)
 {
     PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, false);
@@ -762,6 +913,15 @@
 
     // Check inputs
-    int numParams = -1;                // Number of parameters
-    int numParams2 = 0;                // Number of parameters for part solution (DUAL mode)
+    int numKernels = kernels->num;      // Number of kernel basis functions
+    int numSpatial = PM_SUBTRACTION_POLYTERMS(kernels->spatialOrder); // Number of spatial variations
+    int numBackground = PM_SUBTRACTION_POLYTERMS(kernels->bgOrder); // Number of background terms
+    int numParams = numKernels * numSpatial + 1 + numBackground;    // Number of parameters being solved for
+    int numSolution1 = numParams, numSolution2 = 0;                 // Number of parameters for each solution
+    if (kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+        // An additional image is convolved
+        numSolution2 = numKernels * numSpatial;
+        numParams += numSolution2;
+    }
+
     for (int i = 0; i < stamps->num; i++) {
         pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
@@ -771,32 +931,10 @@
         }
 
-        PS_ASSERT_VECTOR_NON_NULL(stamp->vector1, false);
-        if (numParams == -1) {
-            numParams = stamp->vector1->n;
-        }
-        PS_ASSERT_VECTOR_SIZE(stamp->vector1, (long)numParams, false);
-        PS_ASSERT_VECTOR_TYPE(stamp->vector1, PS_TYPE_F64, false);
-        PS_ASSERT_IMAGE_NON_NULL(stamp->matrix1, false);
-        PS_ASSERT_IMAGE_SIZE(stamp->matrix1, numParams, numParams, false);
-        PS_ASSERT_IMAGE_TYPE(stamp->matrix1, PS_TYPE_F64, false);
-
-        if (kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
-            PS_ASSERT_IMAGE_NON_NULL(stamp->matrix2, false);
-            PS_ASSERT_IMAGE_NON_NULL(stamp->matrixX, false);
-            if (numParams2 == 0) {
-                numParams2 = stamp->matrix2->numCols;
-            }
-            PS_ASSERT_IMAGE_SIZE(stamp->matrix2, numParams2, numParams2, false);
-            PS_ASSERT_IMAGE_SIZE(stamp->matrixX, numParams, numParams2, false);
-            PS_ASSERT_IMAGE_TYPE(stamp->matrix2, PS_TYPE_F64, false);
-            PS_ASSERT_IMAGE_TYPE(stamp->matrixX, PS_TYPE_F64, false);
-            PS_ASSERT_VECTOR_NON_NULL(stamp->vector2, false);
-            PS_ASSERT_VECTOR_SIZE(stamp->vector2, (long)numParams2, false);
-            PS_ASSERT_VECTOR_TYPE(stamp->vector2, PS_TYPE_F64, false);
-        }
-    }
-    if (numParams == -1) {
-        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "No suitable stamps found.");
-        return NULL;
+        PS_ASSERT_VECTOR_NON_NULL(stamp->vector, false);
+        PS_ASSERT_VECTOR_SIZE(stamp->vector, (long)numParams, false);
+        PS_ASSERT_VECTOR_TYPE(stamp->vector, PS_TYPE_F64, false);
+        PS_ASSERT_IMAGE_NON_NULL(stamp->matrix, false);
+        PS_ASSERT_IMAGE_SIZE(stamp->matrix, numParams, numParams, false);
+        PS_ASSERT_IMAGE_TYPE(stamp->matrix, PS_TYPE_F64, false);
     }
 
@@ -814,28 +952,14 @@
         psVectorInit(sumVector, 0.0);
         psImageInit(sumMatrix, 0.0);
+
+        psVector *norms = psVectorAllocEmpty(stamps->num, PS_TYPE_F64); // Normalisations
+
         int numStamps = 0;              // Number of good stamps
         for (int i = 0; i < stamps->num; i++) {
             pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
-
             if (stamp->status == PM_SUBTRACTION_STAMP_USED) {
-
-#ifdef TESTING
-              // XXX double-check for NAN in data:
-                for (int iy = 0; iy < stamp->matrix1->numRows; iy++) {
-                    for (int ix = 0; ix < stamp->matrix1->numCols; ix++) {
-                        if (!isfinite(stamp->matrix1->data.F64[iy][ix])) {
-                            fprintf (stderr, "WARNING: NAN in matrix1\n");
-                        }
-                    }
-                }
-                for (int ix = 0; ix < stamp->vector1->n; ix++) {
-                    if (!isfinite(stamp->vector1->data.F64[ix])) {
-                        fprintf (stderr, "WARNING: NAN in vector1\n");
-                    }
-                }
-#endif
-
-                (void)psBinaryOp(sumMatrix, sumMatrix, "+", stamp->matrix1);
-                (void)psBinaryOp(sumVector, sumVector, "+", stamp->vector1);
+                (void)psBinaryOp(sumMatrix, sumMatrix, "+", stamp->matrix);
+                (void)psBinaryOp(sumVector, sumVector, "+", stamp->vector);
+                psVectorAppend(norms, stamp->norm);
                 pmSubtractionStampPrint(ds9, stamp->x, stamp->y, stamps->footprint, "green");
                 numStamps++;
@@ -845,12 +969,4 @@
         }
 
-#ifdef TESTING
-        for (int ix = 0; ix < sumVector->n; ix++) {
-            if (!isfinite(sumVector->data.F64[ix])) {
-                fprintf (stderr, "WARNING: NAN in vector1\n");
-            }
-        }
-#endif
-
 #if 0
         int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index for background
@@ -858,41 +974,80 @@
 #endif
 
-#ifdef TESTING
-        for (int ix = 0; ix < sumVector->n; ix++) {
-            if (!isfinite(sumVector->data.F64[ix])) {
-                fprintf (stderr, "WARNING: NAN in vector1\n");
-            }
-        }
+        psVector *solution = NULL;                       // Solution to equation!
+        solution = psVectorAlloc(numParams, PS_TYPE_F64);
+        psVectorInit(solution, 0);
+
+#if 0
+        // Regular, straight-forward solution
+        solution = psMatrixSolveSVD(solution, sumMatrix, sumVector, NAN);
+#else
         {
-            psImage *inverse = psMatrixInvert(NULL, sumMatrix, NULL);
-            psFits *fits = psFitsOpen("matrixInv.fits", "w");
-            psFitsWriteImage(fits, NULL, inverse, 0, NULL);
-            psFitsClose(fits);
-            psFree(inverse);
-        }
-        {
-            psImage *X = psMatrixInvert(NULL, sumMatrix, NULL);
-            psImage *Xt = psMatrixTranspose(NULL, X);
-            psImage *XtX = psMatrixMultiply(NULL, Xt, X);
-            psFits *fits = psFitsOpen("matrixErr.fits", "w");
-            psFitsWriteImage(fits, NULL, XtX, 0, NULL);
-            psFitsClose(fits);
-            psFree(X);
-            psFree(Xt);
-            psFree(XtX);
-        }
-#endif
-
-        psVector *permutation = NULL;       // Permutation vector, required for LU decomposition
-        psImage *luMatrix = psMatrixLUDecomposition(NULL, &permutation, sumMatrix);
+            // Solve normalisation and background separately
+            int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+            int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index for background
+
+            psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN); // Statistics for norm
+            if (!psVectorStats(stats, norms, NULL, NULL, 0)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to determine median normalisation");
+                psFree(stats);
+                psFree(sumMatrix);
+                psFree(sumVector);
+                psFree(norms);
+                return false;
+            }
+
+            double normValue = stats->robustMedian;
+            // double bgValue = 0.0;
+
+            psFree(stats);
+
+            fprintf(stderr, "Norm: %lf\n", normValue);
+
+            // Solve kernel components
+            for (int i = 0; i < numSolution1; i++) {
+                sumVector->data.F64[i] -= normValue * sumMatrix->data.F64[normIndex][i];
+
+                sumMatrix->data.F64[i][normIndex] = 0.0;
+                sumMatrix->data.F64[normIndex][i] = 0.0;
+            }
+            sumVector->data.F64[bgIndex] -= normValue * sumMatrix->data.F64[normIndex][bgIndex];
+            sumMatrix->data.F64[bgIndex][normIndex] = 0.0;
+            sumMatrix->data.F64[normIndex][bgIndex] = 0.0;
+
+            sumMatrix->data.F64[normIndex][normIndex] = 1.0;
+            sumVector->data.F64[normIndex] = 0.0;
+
+            solution = psMatrixSolveSVD(solution, sumMatrix, sumVector, NAN);
+
+            solution->data.F64[normIndex] = normValue;
+        }
+# endif
+
+        if (!kernels->solution1) {
+            kernels->solution1 = psVectorAlloc(sumVector->n, PS_TYPE_F64);
+            psVectorInit(kernels->solution1, 0.0);
+        }
+
+        // only update the solutions that we chose to calculate:
+        if (mode & PM_SUBTRACTION_EQUATION_NORM) {
+            int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+            kernels->solution1->data.F64[normIndex] = solution->data.F64[normIndex];
+        }
+        if (mode & PM_SUBTRACTION_EQUATION_BG) {
+            int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index in matrix for background
+            kernels->solution1->data.F64[bgIndex] = solution->data.F64[bgIndex];
+        }
+        if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+            int numKernels = kernels->num;
+            int spatialOrder = kernels->spatialOrder;       // Order of spatial variation
+            int numPoly = PM_SUBTRACTION_POLYTERMS(spatialOrder); // Number of polynomial terms
+            for (int i = 0; i < numKernels * numPoly; i++) {
+                kernels->solution1->data.F64[i] = solution->data.F64[i];
+            }
+        }
+
+        psFree(solution);
+        psFree(sumVector);
         psFree(sumMatrix);
-        if (!luMatrix) {
-            psError(PS_ERR_UNKNOWN, true, "LU Decomposition of least-squares matrix failed.\n");
-            psFree(sumVector);
-            psFree(luMatrix);
-            psFree(permutation);
-            return NULL;
-        }
-        kernels->solution1 = psMatrixLUSolution(kernels->solution1, luMatrix, sumVector, permutation);
 
 #ifdef TESTING
@@ -900,30 +1055,19 @@
         for (int ix = 0; ix < kernels->solution1->n; ix++) {
             if (!isfinite(kernels->solution1->data.F64[ix])) {
-                fprintf (stderr, "WARNING: NAN in vector1\n");
-            }
-        }
-#endif
-
-        psFree(sumVector);
-        psFree(luMatrix);
-        psFree(permutation);
-        if (!kernels->solution1) {
-            psError(PS_ERR_UNKNOWN, true, "Failed to solve the least-squares system.\n");
-            return NULL;
-        }
+                fprintf (stderr, "WARNING: NAN in vector\n");
+            }
+        }
+#endif
+
     } else {
         // Dual convolution solution
 
         // Accumulation of stamp matrices/vectors
-        psImage *sumMatrix1 = psImageAlloc(numParams, numParams, PS_TYPE_F64);
-        psImage *sumMatrix2 = psImageAlloc(numParams2, numParams2, PS_TYPE_F64);
-        psImage *sumMatrixX = psImageAlloc(numParams, numParams2, PS_TYPE_F64);
-        psVector *sumVector1 = psVectorAlloc(numParams, PS_TYPE_F64);
-        psVector *sumVector2 = psVectorAlloc(numParams, PS_TYPE_F64);
-        psImageInit(sumMatrix1, 0.0);
-        psImageInit(sumMatrix2, 0.0);
-        psImageInit(sumMatrixX, 0.0);
-        psVectorInit(sumVector1, 0.0);
-        psVectorInit(sumVector2, 0.0);
+        psImage *sumMatrix = psImageAlloc(numParams, numParams, PS_TYPE_F64);
+        psVector *sumVector = psVectorAlloc(numParams, PS_TYPE_F64);
+        psImageInit(sumMatrix, 0.0);
+        psVectorInit(sumVector, 0.0);
+
+        psVector *norms = psVectorAllocEmpty(stamps->num, PS_TYPE_F64); // Normalisations
 
         int numStamps = 0;              // Number of good stamps
@@ -931,9 +1075,9 @@
             pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
             if (stamp->status == PM_SUBTRACTION_STAMP_USED) {
-                (void)psBinaryOp(sumMatrix1, sumMatrix1, "+", stamp->matrix1);
-                (void)psBinaryOp(sumMatrix2, sumMatrix2, "+", stamp->matrix2);
-                (void)psBinaryOp(sumMatrixX, sumMatrixX, "+", stamp->matrixX);
-                (void)psBinaryOp(sumVector1, sumVector1, "+", stamp->vector1);
-                (void)psBinaryOp(sumVector2, sumVector2, "+", stamp->vector2);
+                (void)psBinaryOp(sumMatrix, sumMatrix, "+", stamp->matrix);
+                (void)psBinaryOp(sumVector, sumVector, "+", stamp->vector);
+
+                psVectorAppend(norms, stamp->norm);
+
                 pmSubtractionStampPrint(ds9, stamp->x, stamp->y, stamps->footprint, "green");
                 numStamps++;
@@ -941,174 +1085,136 @@
         }
 
-        int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index for background
-        calculatePenalty(sumMatrix1, sumVector1, kernels, sumMatrix1->data.F64[bgIndex][bgIndex]);
-        calculatePenalty(sumMatrix2, sumVector2, kernels, -sumMatrix1->data.F64[bgIndex][bgIndex]);
-
-        psImage *sumMatrix = NULL;      // Combined matrix
-        psVector *sumVector = NULL;     // Combined vector
-        calculateEquationDual(&sumMatrix, &sumVector, sumMatrix1, sumMatrix2,
-                              sumMatrixX, sumVector1, sumVector2);
-
 #ifdef TESTING
+        psFitsWriteImageSimple ("sumMatrix.fits", sumMatrix, NULL);
+        psVectorWriteFile("sumVector.dat", sumVector);
+#endif
+
+#if 1
+        // int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index for background
+        // calculatePenalty(sumMatrix, sumVector, kernels, sumMatrix->data.F64[bgIndex][bgIndex]);
+
+        int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+        calculatePenalty(sumMatrix, sumVector, kernels, sumMatrix->data.F64[normIndex][normIndex] / 1000.0);
+#endif
+
+        psVector *solution = NULL;                       // Solution to equation!
+        solution = psVectorAlloc(numParams, PS_TYPE_F64);
+        psVectorInit(solution, 0);
+
+#if 0
+        // Regular, straight-forward solution
+        solution = psMatrixSolveSVD(solution, sumMatrix, sumVector, NAN);
+#else
         {
-            psFits *fits = psFitsOpen("sumMatrix.fits", "w");
-            psFitsWriteImage(fits, NULL, sumMatrix, 0, NULL);
-            psFitsClose(fits);
-        }
-        {
-            psImage *image = psImageAlloc(1, numParams + numParams2, PS_TYPE_F64);
-            psFits *fits = psFitsOpen("sumVector.fits", "w");
-            for (int i = 0; i < numParams + numParams2; i++) {
-                image->data.F64[0][i] = sumVector->data.F64[i];
-            }
-            psFitsWriteImage(fits, NULL, image, 0, NULL);
-            psFree(image);
-            psFitsClose(fits);
-        }
-#endif
-
-        psVector *solution = NULL;                       // Solution to equation!
-        {
-            solution = psMatrixSolveSVD(solution, sumMatrix, sumVector);
-            if (!solution) {
-                psError(PS_ERR_UNKNOWN, false, "SVD solution of least-squares equation failed.\n");
+            // Solve normalisation and background separately
+            int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+            int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index for background
+
+#if 0
+            psImage *normMatrix = psImageAlloc(2, 2, PS_TYPE_F64);
+            psVector *normVector = psVectorAlloc(2, PS_TYPE_F64);
+
+            normMatrix->data.F64[0][0] = sumMatrix->data.F64[normIndex][normIndex];
+            normMatrix->data.F64[1][1] = sumMatrix->data.F64[bgIndex][bgIndex];
+            normMatrix->data.F64[0][1] = normMatrix->data.F64[1][0] = sumMatrix->data.F64[normIndex][bgIndex];
+
+            normVector->data.F64[0] = sumVector->data.F64[normIndex];
+            normVector->data.F64[1] = sumVector->data.F64[bgIndex];
+
+            psVector *normSolution = psMatrixSolveSVD(NULL, normMatrix, normVector, NAN);
+
+            double normValue = normSolution->data.F64[0];
+            double bgValue = normSolution->data.F64[1];
+
+            psFree(normMatrix);
+            psFree(normVector);
+            psFree(normSolution);
+#endif
+
+            psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN); // Statistics for norm
+            if (!psVectorStats(stats, norms, NULL, NULL, 0)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to determine median normalisation");
+                psFree(stats);
                 psFree(sumMatrix);
                 psFree(sumVector);
-                return NULL;
-            }
-
-            int numSpatial = PM_SUBTRACTION_POLYTERMS(kernels->spatialOrder); // Number of spatial variations
-            int numKernels = kernels->num; // Number of kernel basis functions
-
-            // Remove a kernel basis for image 1 from the equation
-#define MASK_BASIS_1(INDEX)                                             \
-            {                                                           \
-                for (int j = 0, index = INDEX; j < numSpatial; j++, index += numKernels) { \
-                    for (int k = 0; k < numParams2; k++) {              \
-                        sumMatrix1->data.F64[k][index] = 0.0;           \
-                        sumMatrix1->data.F64[index][k] = 0.0;           \
-                        sumMatrixX->data.F64[k][index] = 0.0;           \
-                    }                                                   \
-                    sumMatrix1->data.F64[bgIndex][index] = 0.0;         \
-                    sumMatrix1->data.F64[index][bgIndex] = 0.0;         \
-                    sumMatrix1->data.F64[normIndex][index] = 0.0;       \
-                    sumMatrix1->data.F64[index][normIndex] = 0.0;       \
-                    sumMatrix1->data.F64[index][index] = 1.0;           \
-                    sumVector1->data.F64[index] = 0.0;                  \
-                }                                                       \
-            }
-
-            // Remove a kernel basis for image 2 from the equation
-#define MASK_BASIS_2(INDEX)                                             \
-            {                                                           \
-                for (int j = 0, index = INDEX; j < numSpatial; j++, index += numKernels) { \
-                    for (int k = 0; k < numParams2; k++) {              \
-                        sumMatrix2->data.F64[k][index] = 0.0;           \
-                        sumMatrix2->data.F64[index][k] = 0.0;           \
-                        sumMatrixX->data.F64[index][k] = 0.0;           \
-                    }                                                   \
-                    sumMatrix2->data.F64[index][index] = 1.0;           \
-                    sumMatrixX->data.F64[index][normIndex] = 0.0;       \
-                    sumMatrixX->data.F64[index][bgIndex] = 0.0;         \
-                    sumVector2->data.F64[index] = 0.0;                  \
-                }                                                       \
-            }
-
-            #define TOL 1.0e-5
-            int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
-            double norm = fabs(solution->data.F64[normIndex]);  // Normalisation
-            double thresh = norm * TOL;                         // Threshold for low parameters
-            for (int i = 0; i < numKernels; i++) {
-                // Getting 0th order parameter value.  In the presence of spatial variation, the actual value
-                // of the parameter will vary over the image.  We are in effect getting the value in the
-                // centre of the image.  If we use different polynomial functions (e.g., Chebyshev), we may
-                // have to change this to properly determine the value of the parameter at the centre.
-                double param1 = solution->data.F64[i],
-                    param2 = solution->data.F64[numParams + i]; // Parameters of interest
-                bool mask1 = false, mask2 = false;              // Masked the parameter?
-                if (fabs(param1) < thresh) {
-                    psTrace("psModules.imcombine", 7, "Parameter %d: 1 below threshold\n", i);
-                    MASK_BASIS_1(i);
-                    mask1 = true;
-                }
-                if (fabs(param2) < thresh) {
-                    psTrace("psModules.imcombine", 7, "Parameter %d: 2 below threshold\n", i);
-                    MASK_BASIS_2(i);
-                    mask2 = true;
-                }
-
-                if (!mask1 && !mask2) {
-                    if (fabs(param1) < fabs(param2)) {
-                        psTrace("psModules.imcombine", 7, "Parameter %d: 1 < 2\n", i);
-                        MASK_BASIS_1(i);
-                    } else {
-                        psTrace("psModules.imcombine", 7, "Parameter %d: 2 < 1\n", i);
-                        MASK_BASIS_2(i);
-                    }
-                }
-            }
-        }
-
-        calculateEquationDual(&sumMatrix, &sumVector, sumMatrix1, sumMatrix2,
-                              sumMatrixX, sumVector1, sumVector2);
+                psFree(norms);
+                return false;
+            }
+
+            double normValue = stats->robustMedian;
+
+            psFree(stats);
+
+            fprintf(stderr, "Norm: %lf\n", normValue);
+
+            // Solve kernel components
+            for (int i = 0; i < numSolution2; i++) {
+                sumVector->data.F64[i] -= normValue * sumMatrix->data.F64[normIndex][i];
+                sumVector->data.F64[i + numSolution1] -= normValue * sumMatrix->data.F64[normIndex][i + numSolution1];
+
+                sumMatrix->data.F64[i][normIndex] = 0.0;
+                sumMatrix->data.F64[normIndex][i] = 0.0;
+
+                sumMatrix->data.F64[i + numSolution1][normIndex] = 0.0;
+                sumMatrix->data.F64[normIndex][i + numSolution1] = 0.0;
+            }
+            sumVector->data.F64[bgIndex] -= normValue * sumMatrix->data.F64[normIndex][bgIndex];
+            sumMatrix->data.F64[bgIndex][normIndex] = 0.0;
+            sumMatrix->data.F64[normIndex][bgIndex] = 0.0;
+
+            sumMatrix->data.F64[normIndex][normIndex] = 1.0;
+
+            sumVector->data.F64[normIndex] = 0.0;
+
+            solution = psMatrixSolveSVD(solution, sumMatrix, sumVector, NAN);
+
+            solution->data.F64[normIndex] = normValue;
+        }
+#endif
+
 
 #ifdef TESTING
-        {
-            psFits *fits = psFitsOpen("sumMatrixFix.fits", "w");
-            psFitsWriteImage(fits, NULL, sumMatrix, 0, NULL);
-            psFitsClose(fits);
-        }
-        {
-            psImage *image = psImageAlloc(1, numParams + numParams2, PS_TYPE_F64);
-            psFits *fits = psFitsOpen("sumVectorFix.fits", "w");
-            for (int i = 0; i < numParams + numParams2; i++) {
-                image->data.F64[0][i] = sumVector->data.F64[i];
-            }
-            psFitsWriteImage(fits, NULL, image, 0, NULL);
-            psFree(image);
-            psFitsClose(fits);
-        }
-#endif
-
-        solution = psMatrixSolveSVD(solution, sumMatrix, sumVector);
-        if (!solution) {
-            psError(PS_ERR_UNKNOWN, false, "SVD solution of least-squares equation failed.\n");
-            psFree(sumMatrix);
-            psFree(sumVector);
-            return NULL;
-        }
-
-        psFree(sumMatrix1);
-        psFree(sumMatrix2);
-        psFree(sumMatrixX);
-        psFree(sumVector1);
-        psFree(sumVector2);
+        for (int i = 0; i < solution->n; i++) {
+            fprintf(stderr, "Dual solution %d: %lf\n", i, solution->data.F64[i]);
+        }
+#endif
 
         psFree(sumMatrix);
         psFree(sumVector);
 
-#ifdef TESTING
-        {
-            psImage *image = psImageAlloc(1, numParams + numParams2, PS_TYPE_F64);
-            psFits *fits = psFitsOpen("solnVector.fits", "w");
-            for (int i = 0; i < numParams + numParams2; i++) {
-                image->data.F64[0][i] = solution->data.F64[i];
-            }
-            psFitsWriteImage(fits, NULL, image, 0, NULL);
-            psFree(image);
-            psFitsClose(fits);
-        }
-#endif
+        psFree(norms);
 
         if (!kernels->solution1) {
-            kernels->solution1 = psVectorAlloc(numParams, PS_TYPE_F64);
+            kernels->solution1 = psVectorAlloc(numSolution1, PS_TYPE_F64);
+            psVectorInit (kernels->solution1, 0.0);
         }
         if (!kernels->solution2) {
-            kernels->solution2 = psVectorAlloc(numParams2, PS_TYPE_F64);
-        }
-
-        memcpy(kernels->solution1->data.F64, solution->data.F64, numParams * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
-        memcpy(kernels->solution2->data.F64, &solution->data.F64[numParams],
-               numParams2 * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
+            kernels->solution2 = psVectorAlloc(numSolution2, PS_TYPE_F64);
+            psVectorInit (kernels->solution2, 0.0);
+        }
+
+        // only update the solutions that we chose to calculate:
+        if (mode & PM_SUBTRACTION_EQUATION_NORM) {
+            int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+            kernels->solution1->data.F64[normIndex] = solution->data.F64[normIndex];
+        }
+        if (mode & PM_SUBTRACTION_EQUATION_BG) {
+            int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index in matrix for background
+            kernels->solution1->data.F64[bgIndex] = solution->data.F64[bgIndex];
+        }
+        if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+            int numKernels = kernels->num;
+            for (int i = 0; i < numKernels * numSpatial; i++) {
+                // XXX fprintf (stderr, "keep\n");
+                kernels->solution1->data.F64[i] = solution->data.F64[i];
+                kernels->solution2->data.F64[i] = solution->data.F64[i + numSolution1];
+            }
+        }
+
+
+        memcpy(kernels->solution1->data.F64, solution->data.F64,
+               numSolution1 * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
+        memcpy(kernels->solution2->data.F64, &solution->data.F64[numSolution1],
+               numSolution2 * PSELEMTYPE_SIZEOF(PS_TYPE_F64));
 
         psFree(solution);
@@ -1131,10 +1237,55 @@
      }
 
-    pmSubtractionVisualPlotLeastSquares((pmSubtractionStampList *) stamps); //casting away const
+    // pmSubtractionVisualPlotLeastSquares((pmSubtractionStampList *) stamps); //casting away const
     return true;
 }
 
+bool pmSubtractionResidualStats(psVector *fSigRes, psVector *fMaxRes, psVector *fMinRes, psKernel *target, psKernel *source, psKernel *residual, double norm, int footprint) {
+
+    // XXX measure some useful stats on the residuals
+    float sum = 0.0;
+    float peak = 0.0;
+    for (int y = - footprint; y <= footprint; y++) {
+        for (int x = - footprint; x <= footprint; x++) {
+            sum += 0.5*(target->kernel[y][x] + source->kernel[y][x] * norm);
+            peak = PS_MAX(peak, 0.5*(target->kernel[y][x] + source->kernel[y][x] * norm));
+        }
+    }
+
+    // only count pixels with more than X% of the source flux
+    // calculate stdev(dflux)
+    float dflux1 = 0.0;
+    float dflux2 = 0.0;
+    int npix = 0;
+
+    float dmax = 0.0;
+    float dmin = 0.0;
+
+    for (int y = - footprint; y <= footprint; y++) {
+        for (int x = - footprint; x <= footprint; x++) {
+            float dflux = 0.5*(target->kernel[y][x] + source->kernel[y][x] * norm);
+            if (dflux < 0.02*sum) continue;
+            dflux1 += residual->kernel[y][x];
+            dflux2 += PS_SQR(residual->kernel[y][x]);
+            dmax = PS_MAX(residual->kernel[y][x], dmax);
+            dmin = PS_MIN(residual->kernel[y][x], dmin);
+            npix ++;
+        }
+    }
+    float sigma = sqrt(dflux2 / npix - PS_SQR(dflux1/npix));
+    if (!isfinite(sum))  return false;
+    if (!isfinite(dmax)) return false;
+    if (!isfinite(dmin)) return false;
+    if (!isfinite(peak)) return false;
+
+    // fprintf (stderr, "sum: %f, peak: %f, sigma: %f, fsigma: %f, fmax: %f, fmin: %f\n", sum, peak, sigma, sigma/sum, dmax/peak, dmin/peak);
+    psVectorAppend(fSigRes, sigma/sum);
+    psVectorAppend(fMaxRes, dmax/peak);
+    psVectorAppend(fMinRes, dmin/peak);
+    return true;
+}
+
 psVector *pmSubtractionCalculateDeviations(pmSubtractionStampList *stamps,
-                                           const pmSubtractionKernels *kernels)
+                                           pmSubtractionKernels *kernels)
 {
     PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, NULL);
@@ -1151,4 +1302,43 @@
     psKernel *residual = psKernelAlloc(-footprint, footprint, -footprint, footprint); // Residual image
 
+    // set up holding images for the visualization
+    pmSubtractionVisualShowFitInit (stamps);
+
+    psVector *fSigRes = psVectorAllocEmpty(stamps->num, PS_TYPE_F32);
+    psVector *fMinRes = psVectorAllocEmpty(stamps->num, PS_TYPE_F32);
+    psVector *fMaxRes = psVectorAllocEmpty(stamps->num, PS_TYPE_F32);
+
+    // we want to save the residual images for the 9 brightest stamps.
+    // identify the 9 brightest stamps
+    psVector *keepStamps  = psVectorAlloc(stamps->num, PS_TYPE_S32);
+    psVectorInit (keepStamps, 0);
+    {
+        psVector *flux  = psVectorAlloc(stamps->num, PS_TYPE_F32);
+        psVectorInit (flux, 0.0);
+
+        for (int i = 0; i < stamps->num; i++) {
+            pmSubtractionStamp *stamp = stamps->stamps->data[i];
+            if (!isfinite(stamp->flux)) continue;
+            flux->data.F32[i] = stamp->flux;
+        }
+
+        psVector *index = psVectorSortIndex(NULL, flux);
+        for (int i = 0; (i < stamps->num) && (i < 9); i++) {
+            int n = stamps->num - i - 1;
+            keepStamps->data.S32[index->data.S32[n]] = 1;
+            fprintf (stderr, "keeping %d (%d of %d)\n", index->data.S32[n], n, 9);
+        }
+        psFree (flux);
+        psFree (index);
+
+        // this function is called multiple times in the iteration, but
+        // we only know after the interation is done if we will try again.
+        // therefore we must save the sample each time, and blow away the old one
+        // if it exists.
+        psFree (kernels->sampleStamps);
+        kernels->sampleStamps = psArrayAllocEmpty(9);
+    }
+
+    psString log = psStringCopy("Deviations:\n");               // Log message with deviations
     for (int i = 0; i < stamps->num; i++) {
         pmSubtractionStamp *stamp = stamps->stamps->data[i]; // The stamp of interest
@@ -1210,13 +1400,26 @@
                 for (int y = - footprint; y <= footprint; y++) {
                     for (int x = - footprint; x <= footprint; x++) {
-                        residual->kernel[y][x] -= convolution->kernel[y][x] * coefficient;
+                        residual->kernel[y][x] += convolution->kernel[y][x] * coefficient;
                     }
                 }
             }
+
+            // XXX visualize the target, source, convolution and residual
+            pmSubtractionVisualShowFitAddStamp (target, source, residual, background, norm, i);
+
             for (int y = - footprint; y <= footprint; y++) {
                 for (int x = - footprint; x <= footprint; x++) {
-                    residual->kernel[y][x] += target->kernel[y][x] - background - source->kernel[y][x] * norm;
-                }
-            }
+                    residual->kernel[y][x] += background + source->kernel[y][x] * norm - target->kernel[y][x];
+                }
+            }
+
+            if (keepStamps->data.S32[i]) {
+                psImage *sample = psImageCopy(NULL, residual->image, PS_TYPE_F32);
+                psArrayAdd (kernels->sampleStamps, 9, sample);
+                psFree (sample);
+            }
+
+            pmSubtractionResidualStats(fSigRes, fMaxRes, fMinRes, target, source, residual, norm, footprint);
+
         } else {
             // Dual convolution
@@ -1234,13 +1437,24 @@
                 for (int y = - footprint; y <= footprint; y++) {
                     for (int x = - footprint; x <= footprint; x++) {
-                        residual->kernel[y][x] += conv2->kernel[y][x] * coeff2 - conv1->kernel[y][x] * coeff1;
+                        residual->kernel[y][x] += conv2->kernel[y][x] * coeff2 + conv1->kernel[y][x] * coeff1;
                     }
                 }
             }
+
+            // XXX visualize the target, source, convolution and residual
+            pmSubtractionVisualShowFitAddStamp (image2, image1, residual, background, norm, i);
+
             for (int y = - footprint; y <= footprint; y++) {
                 for (int x = - footprint; x <= footprint; x++) {
-                    residual->kernel[y][x] += image2->kernel[y][x] - background - image1->kernel[y][x] * norm;
-                }
-            }
+                    residual->kernel[y][x] += background + image1->kernel[y][x] * norm - image2->kernel[y][x];
+                }
+            }
+            if (keepStamps->data.S32[i]) {
+                psImage *sample = psImageCopy(NULL, residual->image, PS_TYPE_F32);
+                psArrayAdd (kernels->sampleStamps, 9, sample);
+                psFree (sample);
+            }
+
+            pmSubtractionResidualStats(fSigRes, fMaxRes, fMinRes, image1, image2, residual, norm, footprint);
         }
 
@@ -1257,10 +1471,12 @@
         deviations->data.F32[i] = devNorm * deviation;
         psTrace("psModules.imcombine", 5, "Deviation for stamp %d (%d,%d): %f\n",
-                i, (int)(stamp->x + 0.5), (int)(stamp->y + 0.5), deviations->data.F32[i]);
+                i, (int)(stamp->x - 0.5), (int)(stamp->y - 0.5), deviations->data.F32[i]);
+        psStringAppend(&log, "Stamp %d (%d,%d): %f\n",
+                       i, (int)(stamp->x - 0.5), (int)(stamp->y - 0.5), deviations->data.F32[i]);
         if (!isfinite(deviations->data.F32[i])) {
             stamp->status = PM_SUBTRACTION_STAMP_REJECTED;
             psTrace("psModules.imcombine", 5,
                     "Rejecting stamp %d (%d,%d) because of non-finite deviation\n",
-                    i, (int)(stamp->x + 0.5), (int)(stamp->y + 0.5));
+                    i, (int)(stamp->x - 0.5), (int)(stamp->y - 0.5));
             continue;
         }
@@ -1302,7 +1518,681 @@
 
     }
+
+    psLogMsg("psModules.imcombine", PS_LOG_DETAIL, "%s", log);
+    psFree(log);
+
+    // calculate and report the normalization and background for the image center
+    {
+        polyValues = p_pmSubtractionPolynomial(polyValues, kernels->spatialOrder, 0.0, 0.0);
+        double norm = p_pmSubtractionSolutionNorm(kernels); // Normalisation
+        double background = p_pmSubtractionSolutionBackground(kernels, polyValues);// Difference in background
+        psLogMsg("psModules.imcombine", PS_LOG_INFO, "normalization: %f, background: %f", norm, background);
+
+        pmSubtractionVisualShowFit(norm);
+        pmSubtractionVisualPlotFit(kernels);
+
+        psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+        psVectorStats (stats, fSigRes, NULL, NULL, 0);
+        kernels->fSigResMean = stats->robustMedian;
+        kernels->fSigResStdev = stats->robustStdev;
+
+        psStatsInit (stats);
+        psVectorStats (stats, fMaxRes, NULL, NULL, 0);
+        kernels->fMaxResMean = stats->robustMedian;
+        kernels->fMaxResStdev = stats->robustStdev;
+
+        psStatsInit (stats);
+        psVectorStats (stats, fMinRes, NULL, NULL, 0);
+        kernels->fMinResMean = stats->robustMedian;
+        kernels->fMinResStdev = stats->robustStdev;
+
+        // XXX save these values somewhere
+        psLogMsg("psModules.imcombine", PS_LOG_INFO, "fSigma: %f +/- %f, fMaxRes: %f +/- %f, fMinRes: %f +/- %f",
+                 kernels->fSigResMean, kernels->fSigResStdev,
+                 kernels->fMaxResMean, kernels->fMaxResStdev,
+                 kernels->fMinResMean, kernels->fMinResStdev);
+
+        psFree (fSigRes);
+        psFree (fMaxRes);
+        psFree (fMinRes);
+        psFree (stats);
+    }
+
     psFree(residual);
     psFree(polyValues);
 
+
     return deviations;
 }
+
+// we are supplied U, not Ut; w represents a diagonal matrix (also, we apply 1/w instead of w)
+psImage *p_pmSubSolve_wUt (psVector *w, psImage *U) {
+
+    psAssert (w->n == U->numCols, "w and U dimensions do not match");
+
+    // wUt has dimensions transposed relative to Ut.
+    psImage *wUt = psImageAlloc (U->numRows, U->numCols, PS_TYPE_F64);
+    psImageInit (wUt, 0.0);
+
+    for (int i = 0; i < wUt->numCols; i++) {
+        for (int j = 0; j < wUt->numRows; j++) {
+            if (!isfinite(w->data.F64[j])) continue;
+            if (w->data.F64[j] == 0.0) continue;
+            wUt->data.F64[j][i] = U->data.F64[i][j] / w->data.F64[j];
+        }
+    }
+    return wUt;
+}
+
+// XXX this is just standard matrix multiplication: use psMatrixMultiply?
+psImage *p_pmSubSolve_VwUt (psImage *V, psImage *wUt) {
+
+    psAssert (V->numCols == wUt->numRows, "matrix dimensions do not match");
+
+    psImage *Ainv = psImageAlloc (wUt->numCols, V->numRows, PS_TYPE_F64);
+
+    for (int i = 0; i < Ainv->numCols; i++) {
+        for (int j = 0; j < Ainv->numRows; j++) {
+            double sum = 0.0;
+            for (int k = 0; k < V->numCols; k++) {
+                sum += V->data.F64[j][k] * wUt->data.F64[k][i];
+            }
+            Ainv->data.F64[j][i] = sum;
+        }
+    }
+    return Ainv;
+}
+
+// we are supplied U, not Ut
+bool p_pmSubSolve_UtB (psVector **UtB, psImage *U, psVector *B) {
+
+    psAssert (U->numRows == B->n, "U and B dimensions do not match");
+
+    UtB[0] = psVectorRecycle (UtB[0], U->numCols, PS_TYPE_F64);
+
+    for (int i = 0; i < U->numCols; i++) {
+        double sum = 0.0;
+        for (int j = 0; j < U->numRows; j++) {
+            sum += B->data.F64[j] * U->data.F64[j][i];
+        }
+        UtB[0]->data.F64[i] = sum;
+    }
+    return true;
+}
+
+// w is diagonal
+bool p_pmSubSolve_wUtB (psVector **wUtB, psVector *w, psVector *UtB) {
+
+    psAssert (w->n == UtB->n, "w and UtB dimensions do not match");
+
+    // wUt has dimensions transposed relative to Ut.
+    wUtB[0] = psVectorRecycle (wUtB[0], w->n, PS_TYPE_F64);
+    psVectorInit (wUtB[0], 0.0);
+
+    for (int i = 0; i < w->n; i++) {
+        if (!isfinite(w->data.F64[i])) continue;
+        if (w->data.F64[i] == 0.0) continue;
+        wUtB[0]->data.F64[i] = UtB->data.F64[i] / w->data.F64[i];
+    }
+    return true;
+}
+
+// this is basically matrix * vector
+bool p_pmSubSolve_VwUtB (psVector **VwUtB, psImage *V, psVector *wUtB) {
+
+    psAssert (V->numCols == wUtB->n, "V and wUtB dimensions do not match");
+
+    VwUtB[0] = psVectorRecycle (*VwUtB, V->numRows, PS_TYPE_F64);
+
+    for (int j = 0; j < V->numRows; j++) {
+        double sum = 0.0;
+        for (int i = 0; i < V->numCols; i++) {
+            sum += V->data.F64[j][i] * wUtB->data.F64[i];
+        }
+        VwUtB[0]->data.F64[j] = sum;
+    }
+    return true;
+}
+
+// this is basically matrix * vector
+bool p_pmSubSolve_Ax (psVector **B, psImage *A, psVector *x) {
+
+    psAssert (A->numCols == x->n, "A and x dimensions do not match");
+
+    B[0] = psVectorRecycle (*B, A->numRows, PS_TYPE_F64);
+
+    for (int j = 0; j < A->numRows; j++) {
+        double sum = 0.0;
+        for (int i = 0; i < A->numCols; i++) {
+            sum += A->data.F64[j][i] * x->data.F64[i];
+        }
+        B[0]->data.F64[j] = sum;
+    }
+    return true;
+}
+
+// this is basically Vector * vector
+bool p_pmSubSolve_VdV (double *value, psVector *x, psVector *y) {
+
+    psAssert (x->n == y->n, "x and y dimensions do not match");
+
+    double sum = 0.0;
+    for (int i = 0; i < x->n; i++) {
+        sum += x->data.F64[i] * y->data.F64[i];
+    }
+    *value = sum;
+    return true;
+}
+
+bool p_pmSubSolve_y2 (double *y2, pmSubtractionKernels *kernels, const pmSubtractionStampList *stamps) {
+
+    int footprint = stamps->footprint; // Half-size of stamps
+
+    double sum = 0.0;
+    for (int i = 0; i < stamps->num; i++) {
+
+        pmSubtractionStamp *stamp = stamps->stamps->data[i];
+        if (stamp->status != PM_SUBTRACTION_STAMP_USED) continue;
+
+        psKernel *weight = NULL;
+        psKernel *window = NULL;
+        psKernel *input = NULL;
+
+#ifdef USE_WEIGHT
+        weight = stamp->weight;
+#endif
+#ifdef USE_WINDOW
+        window = stamps->window;
+#endif
+
+        switch (kernels->mode) {
+            // MODE_1 : convolve image 1 to match image 2 (and vice versa)
+          case PM_SUBTRACTION_MODE_1:
+            input = stamp->image2;
+            break;
+          case PM_SUBTRACTION_MODE_2:
+            input = stamp->image1;
+            break;
+          default:
+            psAbort ("programming error");
+        }
+
+        for (int y = - footprint; y <= footprint; y++) {
+            for (int x = - footprint; x <= footprint; x++) {
+                double in = input->kernel[y][x];
+                double value = in*in;
+                if (weight) {
+                    float wtVal = weight->kernel[y][x];
+                    value *= wtVal;
+                }
+                if (window) {
+                    float  winVal = window->kernel[y][x];
+                    value *= winVal;
+                }
+                sum += value;
+            }
+        }
+    }
+    *y2 = sum;
+    return true;
+}
+
+double p_pmSubSolve_ChiSquare (pmSubtractionKernels *kernels, const pmSubtractionStampList *stamps) {
+
+    int footprint = stamps->footprint; // Half-size of stamps
+    int numKernels = kernels->num;      // Number of kernels
+
+    double sum = 0.0;
+
+    psKernel *residual = psKernelAlloc(-footprint, footprint, -footprint, footprint); // Residual image
+    psImageInit(residual->image, 0.0);
+
+    psImage *polyValues = NULL;         // Polynomial values
+
+    for (int i = 0; i < stamps->num; i++) {
+
+        pmSubtractionStamp *stamp = stamps->stamps->data[i];
+        if (stamp->status != PM_SUBTRACTION_STAMP_USED) continue;
+
+        psKernel *weight = NULL;
+        psKernel *window = NULL;
+        psKernel *target = NULL;
+        psKernel *source = NULL;
+
+        psArray *convolutions = NULL;
+
+#ifdef USE_WEIGHT
+        weight = stamp->weight;
+#endif
+#ifdef USE_WINDOW
+        window = stamps->window;
+#endif
+
+        switch (kernels->mode) {
+            // MODE_1 : convolve image 1 to match image 2 (and vice versa)
+          case PM_SUBTRACTION_MODE_1:
+            target = stamp->image2;
+            source = stamp->image1;
+            convolutions = stamp->convolutions1;
+            break;
+          case PM_SUBTRACTION_MODE_2:
+            target = stamp->image1;
+            source = stamp->image2;
+            convolutions = stamp->convolutions2;
+            break;
+          default:
+            psAbort ("programming error");
+        }
+
+        // Calculate coefficients of the kernel basis functions
+        polyValues = p_pmSubtractionPolynomial(polyValues, kernels->spatialOrder, stamp->xNorm, stamp->yNorm);
+        double norm = p_pmSubtractionSolutionNorm(kernels); // Normalisation
+        double background = p_pmSubtractionSolutionBackground(kernels, polyValues);// Difference in background
+
+        psImageInit(residual->image, 0.0);
+        for (int j = 0; j < numKernels; j++) {
+            psKernel *convolution = convolutions->data[j]; // Convolution
+            double coefficient = p_pmSubtractionSolutionCoeff(kernels, polyValues, j, false); // Coefficient
+            for (int y = - footprint; y <= footprint; y++) {
+                for (int x = - footprint; x <= footprint; x++) {
+                    residual->kernel[y][x] -= convolution->kernel[y][x] * coefficient;
+                }
+            }
+        }
+
+        for (int y = - footprint; y <= footprint; y++) {
+            for (int x = - footprint; x <= footprint; x++) {
+                double resid = target->kernel[y][x] - background - source->kernel[y][x] * norm + residual->kernel[y][x];
+                double value = PS_SQR(resid);
+                if (weight) {
+                    float wtVal = weight->kernel[y][x];
+                    value *= wtVal;
+                }
+                if (window) {
+                    float  winVal = window->kernel[y][x];
+                    value *= winVal;
+                }
+                sum += value;
+            }
+        }
+    }
+    psFree (polyValues);
+    psFree (residual);
+
+    return sum;
+}
+
+bool p_pmSubSolve_SetWeights (psVector *wApply, psVector *w, psVector *wMask) {
+
+    for (int i = 0; i < w->n; i++) {
+        wApply->data.F64[i] = wMask->data.U8[i] ? 0.0 : w->data.F64[i];
+    }
+    return true;
+}
+
+// we are supplied V and w; w represents a diagonal matrix (also, we apply 1/w instead of w)
+psImage *p_pmSubSolve_Xvar (psImage *V, psVector *w) {
+
+    psAssert (w->n == V->numCols, "w and U dimensions do not match");
+
+    psImage *Vn = psImageAlloc (V->numCols, V->numRows, PS_TYPE_F64);
+    psImageInit (Vn, 0.0);
+
+    // generate Vn = V * w^{-1}
+    for (int j = 0; j < Vn->numRows; j++) {
+        for (int i = 0; i < Vn->numCols; i++) {
+            if (!isfinite(w->data.F64[i])) continue;
+            if (w->data.F64[i] == 0.0) continue;
+            Vn->data.F64[j][i] = V->data.F64[j][i] / w->data.F64[i];
+        }
+    }
+
+    psImage *Xvar = psImageAlloc (V->numCols, V->numRows, PS_TYPE_F64);
+    psImageInit (Xvar, 0.0);
+
+    // generate Xvar = Vn * Vn^T
+    for (int j = 0; j < Vn->numRows; j++) {
+        for (int i = 0; i < Vn->numCols; i++) {
+            double sum = 0.0;
+            for (int k = 0; k < Vn->numCols; k++) {
+                sum += Vn->data.F64[k][i]*Vn->data.F64[k][j];
+            }
+            Xvar->data.F64[j][i] = sum;
+        }
+    }
+    return Xvar;
+}
+
+// I get confused by the index values between the image vs matrix usage:  In terms
+// of the elements of an image A(x,y) = A->data.F64[y][x] = A_x,y, a matrix
+// multiplication is: A_k,j * B_i,k = C_i,j
+
+
+bool psFitsWriteImageSimple (char *filename, psImage *image, psMetadata *header) {
+
+    psFits *fits = psFitsOpen(filename, "w");
+    psFitsWriteImage(fits, header, image, 0, NULL);
+    psFitsClose(fits);
+
+    return true;
+}
+
+bool psVectorWriteFile (char *filename, const psVector *vector) {
+
+    FILE *f = fopen (filename, "w");
+    int fd = fileno(f);
+    p_psVectorPrint (fd, vector, "unnamed");
+    fclose (f);
+
+    return true;
+}
+
+
+# if 0
+
+#ifdef TESTING
+        psFitsWriteImageSimple("A.fits", sumMatrix, NULL);
+        psVectorWriteFile ("B.dat", sumVector);
+#endif
+
+# define SVD_ANALYSIS 0
+# define COEFF_SIG 0.0
+# define SVD_TOL 0.0
+
+        // Use SVD to determine the kernel coeffs (and validate)
+        if (SVD_ANALYSIS) {
+
+            // We have sumVector and sumMatrix.  we are trying to solve the following equation:
+            // sumMatrix * x = sumVector.
+
+            // we can use any standard matrix inversion to solve this.  However, the basis
+            // functions in general have substantial correlation, so that the solution may be
+            // somewhat poorly determined or unstable.  If not numerically ill-conditioned, the
+            // system of equations may be statistically ill-conditioned.  Noise in the image
+            // will drive insignificant, but correlated, terms in the solution.  To avoid these
+            // problems, we can use SVD to identify numerically unconstrained values and to
+            // avoid statistically badly determined value.
+
+            // A = sumMatrix, B = sumVector
+            // SVD: A = U w V^T  -> A^{-1} = V (1/w) U^T
+            // x = V (1/w) (U^T B)
+            // \sigma_x = sqrt(diag(A^{-1}))
+            // solve for x and A^{-1} to get x & dx
+            // identify the elements of (1/w) that are nan (1/0.0) -> set to 0.0
+            // identify the elements of x that are insignificant (x / dx < 1.0? < 0.5?) -> set to 0.0
+
+            // If I use the SVD trick to re-condition the matrix, I need to break out the
+            // kernel and normalization terms from the background term.
+            // XXX is this true?  or was this due to an error in the analysis?
+
+            int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index in matrix for background
+
+            // now pull out the kernel elements into their own square matrix
+            psImage  *kernelMatrix = psImageAlloc  (sumMatrix->numCols - 1, sumMatrix->numRows - 1, PS_TYPE_F64);
+            psVector *kernelVector = psVectorAlloc (sumMatrix->numCols - 1, PS_TYPE_F64);
+
+            for (int ix = 0, kx = 0; ix < sumMatrix->numCols; ix++) {
+                if (ix == bgIndex) continue;
+                for (int iy = 0, ky = 0; iy < sumMatrix->numRows; iy++) {
+                    if (iy == bgIndex) continue;
+                    kernelMatrix->data.F64[ky][kx] = sumMatrix->data.F64[iy][ix];
+                    ky++;
+                }
+                kernelVector->data.F64[kx] = sumVector->data.F64[ix];
+                kx++;
+            }
+
+            psImage *U = NULL;
+            psImage *V = NULL;
+            psVector *w = NULL;
+            if (!psMatrixSVD (&U, &w, &V, kernelMatrix)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to perform SVD on sumMatrix\n");
+                return NULL;
+            }
+
+            // calculate A_inverse:
+            // Ainv = V * w * U^T
+            psImage *wUt  = p_pmSubSolve_wUt (w, U);
+            psImage *Ainv = p_pmSubSolve_VwUt (V, wUt);
+            psImage *Xvar = NULL;
+            psFree (wUt);
+
+# ifdef TESTING
+            // kernel terms:
+            for (int i = 0; i < w->n; i++) {
+                fprintf (stderr, "w: %f\n", w->data.F64[i]);
+            }
+# endif
+            // loop over w adding in more and more of the values until chisquare is no longer
+            // dropping significantly.
+            // XXX this does not seem to work very well: we seem to need all terms even for
+            // simple cases...
+
+            psVector *Xsvd = NULL;
+            {
+                psVector *Ax = NULL;
+                psVector *UtB = NULL;
+                psVector *wUtB = NULL;
+
+                psVector *wApply = psVectorAlloc(w->n, PS_TYPE_F64);
+                psVector *wMask = psVectorAlloc(w->n, PS_TYPE_U8);
+                psVectorInit (wMask, 1); // start by masking everything
+
+                double chiSquareLast = NAN;
+                int maxWeight = 0;
+
+                double Axx, Bx, y2;
+
+                // XXX this is an attempt to exclude insignificant modes.
+                // it was not successful with the ISIS kernel set: removing even
+                // the least significant mode leaves additional ringing / noise
+                // because the terms are so coupled.
+                for (int k = 0; false && (k < w->n); k++) {
+
+                    // unmask the k-th weight
+                    wMask->data.U8[k] = 0;
+                    p_pmSubSolve_SetWeights(wApply, w, wMask);
+
+                    // solve for x:
+                    // x = V * w * (U^T * B)
+                    p_pmSubSolve_UtB (&UtB, U, kernelVector);
+                    p_pmSubSolve_wUtB (&wUtB, wApply, UtB);
+                    p_pmSubSolve_VwUtB (&Xsvd, V, wUtB);
+
+                    // chi-square for this system of equations:
+                    // chi-square = sum over terms of: (Ax - B)*x - b*x - y^2
+                    // y^2 = \sum_stamps \sum_pixels input->kernel[y][x]^2
+                    p_pmSubSolve_Ax (&Ax, kernelMatrix, Xsvd);
+                    p_pmSubSolve_VdV (&Axx, Ax, Xsvd);
+                    p_pmSubSolve_VdV (&Bx, kernelVector, Xsvd);
+                    p_pmSubSolve_y2 (&y2, kernels, stamps);
+
+                    // apparently, this works (compare with the brute force value below
+                    double chiSquare = Axx - 2.0*Bx + y2;
+                    double deltaChi = (k == 0) ? chiSquare : chiSquareLast - chiSquare;
+                    chiSquareLast = chiSquare;
+
+                    // fprintf (stderr, "chi square = %f, delta: %f\n", chiSquare, deltaChi);
+                    if (k && !maxWeight && (deltaChi < 1.0)) {
+                        maxWeight = k;
+                    }
+                }
+
+                // keep all terms or we get extra ringing
+                maxWeight = w->n;
+                psVectorInit (wMask, 1);
+                for (int k = 0; k < maxWeight; k++) {
+                    wMask->data.U8[k] = 0;
+                }
+                p_pmSubSolve_SetWeights(wApply, w, wMask);
+
+                // solve for x:
+                // x = V * w * (U^T * B)
+                p_pmSubSolve_UtB (&UtB, U, kernelVector);
+                p_pmSubSolve_wUtB (&wUtB, wApply, UtB);
+                p_pmSubSolve_VwUtB (&Xsvd, V, wUtB);
+
+                // chi-square for this system of equations:
+                // chi-square = sum over terms of: (Ax - B)*x - b*x - y^2
+                // y^2 = \sum_stamps \sum_pixels input->kernel[y][x]^2
+                p_pmSubSolve_Ax (&Ax, kernelMatrix, Xsvd);
+                p_pmSubSolve_VdV (&Axx, Ax, Xsvd);
+                p_pmSubSolve_VdV (&Bx, kernelVector, Xsvd);
+                p_pmSubSolve_y2 (&y2, kernels, stamps);
+
+                // apparently, this works (compare with the brute force value below
+                double chiSquare = Axx - 2.0*Bx + y2;
+                psLogMsg ("psModules.imcombine", PS_LOG_INFO, "model kernel with %d terms; chi square = %f\n", maxWeight, chiSquare);
+
+                // re-calculate A^{-1} to get new variances:
+                // Ainv = V * w * U^T
+                // XXX since we keep all terms, this is identical to Ainv
+                psImage *wUt  = p_pmSubSolve_wUt (wApply, U);
+                Xvar = p_pmSubSolve_VwUt (V, wUt);
+                psFree (wUt);
+
+                psFree (Ax);
+                psFree (UtB);
+                psFree (wUtB);
+                psFree (wApply);
+                psFree (wMask);
+            }
+
+            // copy the kernel solutions to the full solution vector:
+            solution = psVectorAlloc(sumVector->n, PS_TYPE_F64);
+            solutionErr = psVectorAlloc(sumVector->n, PS_TYPE_F64);
+
+            for (int ix = 0, kx = 0; ix < sumVector->n; ix++) {
+                if (ix == bgIndex) {
+                    solution->data.F64[ix] = 0;
+                    solutionErr->data.F64[ix] = 0.001;
+                    continue;
+                }
+                solutionErr->data.F64[ix] = sqrt(Ainv->data.F64[kx][kx]);
+                solution->data.F64[ix] = Xsvd->data.F64[kx];
+                kx++;
+            }
+
+            psFree (kernelMatrix);
+            psFree (kernelVector);
+
+            psFree (U);
+            psFree (V);
+            psFree (w);
+
+            psFree (Ainv);
+            psFree (Xsvd);
+        } else {
+            psVector *permutation = NULL;       // Permutation vector, required for LU decomposition
+            psImage *luMatrix = psMatrixLUDecomposition(NULL, &permutation, sumMatrix);
+            if (!luMatrix) {
+                psError(PS_ERR_UNKNOWN, true, "LU Decomposition of least-squares matrix failed.\n");
+                psFree(solution);
+                psFree(sumVector);
+                psFree(sumMatrix);
+                psFree(luMatrix);
+                psFree(permutation);
+                return NULL;
+            }
+
+            solution = psMatrixLUSolution(NULL, luMatrix, sumVector, permutation);
+            psFree(luMatrix);
+            psFree(permutation);
+            if (!solution) {
+                psError(PS_ERR_UNKNOWN, true, "Failed to solve the least-squares system.\n");
+                psFree(solution);
+                psFree(sumVector);
+                psFree(sumMatrix);
+                return NULL;
+            }
+
+            // XXX LUD does not provide A^{-1}?  fake the error for now
+            solutionErr = psVectorAlloc(sumVector->n, PS_TYPE_F64);
+            for (int ix = 0; ix < sumVector->n; ix++) {
+                solutionErr->data.F64[ix] = 0.1*solution->data.F64[ix];
+            }
+        }
+
+        if (!kernels->solution1) {
+            kernels->solution1 = psVectorAlloc (sumVector->n, PS_TYPE_F64);
+            psVectorInit (kernels->solution1, 0.0);
+        }
+
+        // only update the solutions that we chose to calculate:
+        if (mode & PM_SUBTRACTION_EQUATION_NORM) {
+            int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+            kernels->solution1->data.F64[normIndex] = solution->data.F64[normIndex];
+        }
+        if (mode & PM_SUBTRACTION_EQUATION_BG) {
+            int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index in matrix for background
+            kernels->solution1->data.F64[bgIndex] = solution->data.F64[bgIndex];
+        }
+        if (mode & PM_SUBTRACTION_EQUATION_KERNELS) {
+            int numKernels = kernels->num;
+            int spatialOrder = kernels->spatialOrder;       // Order of spatial variation
+            int numPoly = PM_SUBTRACTION_POLYTERMS(spatialOrder); // Number of polynomial terms
+            for (int i = 0; i < numKernels * numPoly; i++) {
+                // XXX fprintf (stderr, "%f +/- %f (%f) -> ", solution->data.F64[i], solutionErr->data.F64[i], fabs(solution->data.F64[i]/solutionErr->data.F64[i]));
+                if (fabs(solution->data.F64[i] / solutionErr->data.F64[i]) < COEFF_SIG) {
+                    // XXX fprintf (stderr, "drop\n");
+                    kernels->solution1->data.F64[i] = 0.0;
+                } else {
+                    // XXX fprintf (stderr, "keep\n");
+                    kernels->solution1->data.F64[i] = solution->data.F64[i];
+                }
+            }
+        }
+        // double chiSquare = p_pmSubSolve_ChiSquare (kernels, stamps);
+        // fprintf (stderr, "chi square Brute = %f\n", chiSquare);
+
+        psFree(solution);
+        psFree(sumVector);
+        psFree(sumMatrix);
+# endif
+
+#ifdef TESTING
+              // XXX double-check for NAN in data:
+                for (int iy = 0; iy < stamp->matrix->numRows; iy++) {
+                    for (int ix = 0; ix < stamp->matrix->numCols; ix++) {
+                        if (!isfinite(stamp->matrix->data.F64[iy][ix])) {
+                            fprintf (stderr, "WARNING: NAN in matrix\n");
+                        }
+                    }
+                }
+                for (int ix = 0; ix < stamp->vector->n; ix++) {
+                    if (!isfinite(stamp->vector->data.F64[ix])) {
+                        fprintf (stderr, "WARNING: NAN in vector\n");
+                    }
+                }
+#endif
+
+#ifdef TESTING
+        for (int ix = 0; ix < sumVector->n; ix++) {
+            if (!isfinite(sumVector->data.F64[ix])) {
+                fprintf (stderr, "WARNING: NAN in vector\n");
+            }
+        }
+#endif
+
+#ifdef TESTING
+        for (int ix = 0; ix < sumVector->n; ix++) {
+            if (!isfinite(sumVector->data.F64[ix])) {
+                fprintf (stderr, "WARNING: NAN in vector\n");
+            }
+        }
+        {
+            psImage *inverse = psMatrixInvert(NULL, sumMatrix, NULL);
+            psFitsWriteImageSimple("matrixInv.fits", inverse, NULL);
+            psFree(inverse);
+        }
+        {
+            psImage *X = psMatrixInvert(NULL, sumMatrix, NULL);
+            psImage *Xt = psMatrixTranspose(NULL, X);
+            psImage *XtX = psMatrixMultiply(NULL, Xt, X);
+            psFitsWriteImageSimple("matrixErr.fits", XtX, NULL);
+            psFree(X);
+            psFree(Xt);
+            psFree(XtX);
+        }
+#endif
+
