@@ -504,6 +504,132 @@ int SpruxFFISolver::solve(const double* csr_data, const double* rhs, double* x_o
504504 return itersUsedCpu;
505505}
506506
507+ // ---------------------------------------------------------------------------
508+ // solveOnly(): reuse cached factorization for chord Newton.
509+ // No equilibrate, no scatter, no factorLU — just permute RHS + solveLU + refine.
510+ // Uses equilibration scales and factored data from the most recent solve().
511+ // ---------------------------------------------------------------------------
512+
513+ int SpruxFFISolver::solveOnly (const double * csr_data, const double * rhs, double * x_out) {
514+ auto & d = *impl_;
515+ const int64_t n = d.n ;
516+ const auto & perm = *d.perm ;
517+
518+ // Use equilibration scales from the most recent solve() (stored in d.rowScale/colScale
519+ // for CPU path, or in the last-used slot for Metal path).
520+
521+ #ifdef SPRUX_USE_METAL
522+ if (d.useMetal ) {
523+ auto & symCtx = d.solver ->internalSymbolicContext ();
524+ auto & metalCtx = MetalContext::instance ();
525+
526+ // The last completed slot has the factored data (solve() calls begin+end, swaps curSlot).
527+ int factSlot = 1 - d.curSlot ;
528+ auto & slot = d.slots [factSlot];
529+
530+ // Permute RHS using the cached equilibration scales from the factored solve
531+ for (int64_t j = 0 ; j < n; j++) {
532+ slot.xGpu .ptr ()[perm[j]] = float (slot.rowScale [j] * rhs[d.preproc .rowPerm [j]]);
533+ }
534+
535+ // GPU solve only (no factor) — reuse factored dataGpu + devPivots
536+ void * cmdBuf = metalCtx.createCommandBuffer ();
537+ void * encoder = metalCtx.createComputeEncoder (cmdBuf);
538+ symCtx.setExternalEncoder (cmdBuf, encoder);
539+
540+ d.solver ->solveLU (slot.dataGpu .ptr (), slot.devPivots .ptr (), slot.xGpu .ptr (), n, 1 ,
541+ *d.solveCtx , PivotLocation::Device);
542+
543+ // Iterative refinement with the NEW csr_data (for accurate f64 SpMV)
544+ std::fill (slot.xAccum .begin (), slot.xAccum .end (), 0.0 );
545+ double bNormSq = 0.0 ;
546+ for (int64_t j = 0 ; j < n; j++) bNormSq += rhs[j] * rhs[j];
547+
548+ int itersUsed = 0 ;
549+ for (int iter = 0 ; iter < d.maxRefine ; iter++) {
550+ symCtx.clearExternalEncoder ();
551+
552+ for (int64_t j = 0 ; j < n; j++) {
553+ slot.xAccum [j] += slot.colScale [j] * double (slot.xGpu .ptr ()[perm[j]]);
554+ }
555+
556+ double resNormSq = 0.0 ;
557+ for (int64_t j = 0 ; j < n; j++) {
558+ int64_t srcRow = d.preproc .rowPerm [j];
559+ double sum = 0.0 ;
560+ for (int64_t k = d.csrIndptr [srcRow]; k < d.csrIndptr [srcRow + 1 ]; k++) {
561+ sum += csr_data[k] * slot.xAccum [d.csrIndices [k]];
562+ }
563+ double residual = rhs[srcRow] - sum;
564+ resNormSq += residual * residual;
565+ slot.xGpu .ptr ()[perm[j]] = float (slot.rowScale [j] * residual);
566+ }
567+ itersUsed = iter + 1 ;
568+
569+ if (d.refineTol > 0 && resNormSq <= d.refineTol * d.refineTol * std::max (bNormSq, 1e-300 )) {
570+ break ;
571+ }
572+
573+ void * newCmdBuf = metalCtx.createCommandBuffer ();
574+ void * newEncoder = metalCtx.createComputeEncoder (newCmdBuf);
575+ symCtx.setExternalEncoder (newCmdBuf, newEncoder);
576+
577+ d.solver ->solveLU (slot.dataGpu .ptr (), slot.devPivots .ptr (), slot.xGpu .ptr (), n, 1 ,
578+ *d.solveCtx , PivotLocation::Device);
579+ }
580+
581+ symCtx.clearExternalEncoder ();
582+
583+ for (int64_t j = 0 ; j < n; j++) {
584+ x_out[j] = slot.xAccum [j] + slot.colScale [j] * double (slot.xGpu .ptr ()[perm[j]]);
585+ }
586+ return itersUsed;
587+ }
588+ #endif
589+
590+ // CPU fallback — reuse cached dataCpu + pivotsCpu (from last solve())
591+ auto & bp = d.bpCpu ;
592+ for (int64_t j = 0 ; j < n; j++) {
593+ bp[perm[j]] = float (d.rowScale [j] * rhs[d.preproc .rowPerm [j]]);
594+ }
595+ d.solver ->solveLU (d.dataCpu .data (), d.pivotsCpu .data (), bp.data (), n, 1 );
596+
597+ for (int64_t j = 0 ; j < n; j++) {
598+ x_out[j] = d.colScale [j] * double (bp[perm[j]]);
599+ }
600+
601+ double bNormSq = 0.0 ;
602+ for (int64_t j = 0 ; j < n; j++) bNormSq += rhs[j] * rhs[j];
603+ int itersUsed = 0 ;
604+
605+ for (int iter = 0 ; iter < d.maxRefine ; iter++) {
606+ std::fill (bp.begin (), bp.end (), 0 .0f );
607+ double resNormSq = 0.0 ;
608+ for (int64_t j = 0 ; j < n; j++) {
609+ int64_t srcRow = d.preproc .rowPerm [j];
610+ double sum = 0.0 ;
611+ for (int64_t k = d.csrIndptr [srcRow]; k < d.csrIndptr [srcRow + 1 ]; k++) {
612+ sum += csr_data[k] * x_out[d.csrIndices [k]];
613+ }
614+ double residual = rhs[srcRow] - sum;
615+ resNormSq += residual * residual;
616+ bp[perm[j]] = float (d.rowScale [j] * residual);
617+ }
618+ itersUsed = iter + 1 ;
619+
620+ if (d.refineTol > 0 && resNormSq <= d.refineTol * d.refineTol * std::max (bNormSq, 1e-300 )) {
621+ break ;
622+ }
623+
624+ d.solver ->solveLU (d.dataCpu .data (), d.pivotsCpu .data (), bp.data (), n, 1 );
625+
626+ for (int64_t j = 0 ; j < n; j++) {
627+ x_out[j] += d.colScale [j] * double (bp[perm[j]]);
628+ }
629+ }
630+ return itersUsed;
631+ }
632+
507633// ---------------------------------------------------------------------------
508634// dot(): sparse matrix-vector multiply (CPU, f64, no permutation)
509635// ---------------------------------------------------------------------------
0 commit comments