2121#include < algorithm>
2222#include < cmath>
2323#include < cstddef>
24+ #include < fmt/format.h>
2425#include < vector>
2526
2627#include " petscerror.h"
@@ -61,6 +62,15 @@ PetscErrorCode FormFunctionForColoring(void* UNUSED(snes), Vec x, Vec f, void* c
6162 return static_cast <SNESSolver*>(ctx)->snes_function (x, f, true );
6263}
6364
65+ PetscErrorCode FormRawFunctionForColoring (void * UNUSED (snes), Vec x, Vec f, void* ctx) {
66+ return static_cast <SNESSolver*>(ctx)->raw_rhs_function (x, f, true );
67+ }
68+
69+ PetscErrorCode FormScaledFunctionForColoring (void * UNUSED (snes), Vec x, Vec f,
70+ void* ctx) {
71+ return static_cast <SNESSolver*>(ctx)->scaled_rhs_function (x, f, true );
72+ }
73+
6474PetscErrorCode snesPCapply (PC pc, Vec x, Vec y) {
6575 // Get the context
6676 SNESSolver* s;
@@ -71,6 +81,8 @@ PetscErrorCode snesPCapply(PC pc, Vec x, Vec y) {
7181
7282PetscErrorCode ComputeJacobianScaledColor (SNES snes, Vec x1, Mat Jac, Mat Jac_new,
7383 void * ctx);
84+ PetscErrorCode ComputeJacobianDefaultMaybeExport (SNES snes, Vec x1, Mat Jac, Mat Jac_new,
85+ void * ctx);
7486} // namespace
7587
7688PetscErrorCode SNESSolver::FDJinitialise () {
@@ -111,9 +123,9 @@ PetscErrorCode SNESSolver::FDJinitialise() {
111123 nullptr , &Jfd);
112124
113125 if (matrix_free_operator) {
114- SNESSetJacobian (snes, Jmf, Jfd, SNESComputeJacobianDefault , this );
126+ SNESSetJacobian (snes, Jmf, Jfd, ComputeJacobianDefaultMaybeExport , this );
115127 } else {
116- SNESSetJacobian (snes, Jfd, Jfd, SNESComputeJacobianDefault , this );
128+ SNESSetJacobian (snes, Jfd, Jfd, ComputeJacobianDefaultMaybeExport , this );
117129 }
118130
119131 MatSetOption (Jfd, MAT_NEW_NONZERO_ALLOCATION_ERR , PETSC_FALSE );
@@ -364,7 +376,99 @@ SNESSolver::SNESSolver(Options* opts)
364376 .withDefault<BoutReal>(100 .)),
365377 asinh_vars((*options)["asinh_vars"]
366378 .doc(" Apply asinh() to all variables?" )
367- .withDefault<bool>(false )) {}
379+ .withDefault<bool>(false )),
380+ save_jacobian((*options)["save_jacobian"]
381+ .doc(" Save Jacobian matrices for diagnostics?" )
382+ .withDefault<bool>(false )),
383+ jacobian_export_kind((*options)["jacobian_export_kind"]
384+ .doc(" Which Jacobian to save: system, scaled, rhs" )
385+ .withDefault(JacobianExportKind::system)),
386+ jacobian_export_prefix(
387+ (*options)["jacobian_export_prefix"]
388+ .doc(" Prefix for saved Jacobian matrix and metadata files" )
389+ .withDefault(" jacobian" )),
390+ jacobian_export_format((*options)["jacobian_export_format"]
391+ .doc(" Format for saved Jacobian matrices: binary, ascii" )
392+ .withDefault(PetscMatrixExportFormat::binary)) {}
393+
394+ std::string SNESSolver::getJacobianExportStem (JacobianExportKind kind) {
395+ return fmt::format (" {}_{}_{:06d}" , jacobian_export_prefix, toString (kind),
396+ jacobian_export_counter++);
397+ }
398+
399+ std::string SNESSolver::getJacobianMatrixFilename (const std::string& stem) const {
400+ return stem
401+ + (jacobian_export_format == PetscMatrixExportFormat::binary ? " .dat" : " .txt" );
402+ }
403+
404+ PetscErrorCode
405+ SNESSolver::exportMatrixAndMetadata (const PetscPreconditioner& preconditioner,
406+ const std::string& stem) {
407+ if (!jacobian_metadata_written) {
408+ writeJacobianMetadataJson (jacobian_export_prefix + " _metadata.json" , " snes" );
409+ jacobian_metadata_written = true ;
410+ }
411+
412+ PetscCall (
413+ preconditioner.saveMatrix (getJacobianMatrixFilename (stem), jacobian_export_format));
414+ PetscFunctionReturn (PETSC_SUCCESS );
415+ }
416+
417+ PetscErrorCode SNESSolver::saveDiagnosticJacobian (JacobianExportKind kind, Vec x_solver) {
418+ PetscPreconditioner diagnostic_preconditioner;
419+ Field3D index = globalIndex (0 );
420+ PetscCall (diagnostic_preconditioner.createJacobianPattern (
421+ index, *options, nlocal, n2Dvars (), n3Dvars (), BoutComm::get ()));
422+
423+ if (kind == JacobianExportKind::rhs) {
424+ PetscCall (diagnostic_preconditioner.updateColoring (FormRawFunctionForColoring, this ));
425+ } else {
426+ PetscCall (
427+ diagnostic_preconditioner.updateColoring (FormScaledFunctionForColoring, this ));
428+ }
429+
430+ Vec x_evaluate = x_solver;
431+ Vec physical_x{nullptr };
432+ if (kind == JacobianExportKind::rhs) {
433+ PetscCall (VecDuplicate (x_solver, &physical_x));
434+ PetscCall (toPhysicalState (x_solver, physical_x));
435+ x_evaluate = physical_x;
436+ }
437+
438+ Mat diagnostic_jacobian = diagnostic_preconditioner.jacobian ();
439+ PetscCall (MatZeroEntries (diagnostic_jacobian));
440+ PetscCall (SNESComputeJacobianDefaultColor (snes, x_evaluate, diagnostic_jacobian,
441+ diagnostic_jacobian,
442+ diagnostic_preconditioner.coloring ()));
443+ PetscCall (
444+ exportMatrixAndMetadata (diagnostic_preconditioner, getJacobianExportStem (kind)));
445+
446+ if (physical_x != nullptr ) {
447+ PetscCall (VecDestroy (&physical_x));
448+ }
449+
450+ PetscFunctionReturn (PETSC_SUCCESS );
451+ }
452+
453+ PetscErrorCode SNESSolver::maybeExportJacobian (Mat system_jacobian, Vec x_solver) {
454+ if (!save_jacobian) {
455+ PetscFunctionReturn (PETSC_SUCCESS );
456+ }
457+
458+ if (jacobian_export_kind == JacobianExportKind::system) {
459+ if (!jacobian_metadata_written) {
460+ writeJacobianMetadataJson (jacobian_export_prefix + " _metadata.json" , " snes" );
461+ jacobian_metadata_written = true ;
462+ }
463+ PetscCall (PetscPreconditioner::saveMatrix (
464+ system_jacobian,
465+ getJacobianMatrixFilename (getJacobianExportStem (jacobian_export_kind)),
466+ jacobian_export_format));
467+ PetscFunctionReturn (PETSC_SUCCESS );
468+ }
469+
470+ PetscFunctionReturn (saveDiagnosticJacobian (jacobian_export_kind, x_solver));
471+ }
368472
369473int SNESSolver::init () {
370474 Solver::init ();
@@ -1128,13 +1232,13 @@ PetscErrorCode SNESSolver::updateResiduals(Vec x) {
11281232 const BoutReal* current_residual = nullptr ;
11291233 if (diagnose) {
11301234 // Call RHS function to get time derivatives
1131- PetscCall (rhs_function (x, deriv, false ));
1235+ PetscCall (scaled_rhs_function (x, deriv, false ));
11321236
11331237 // Reading the residual vectors
11341238 PetscCall (VecGetArrayRead (deriv, ¤t_residual));
11351239 } else {
11361240 // Call RHS function to get time derivatives
1137- PetscCall (rhs_function (x, snes_f, false ));
1241+ PetscCall (scaled_rhs_function (x, snes_f, false ));
11381242
11391243 // Reading the residual vectors
11401244 PetscCall (VecGetArrayRead (snes_f, ¤t_residual));
@@ -1415,34 +1519,34 @@ BoutReal SNESSolver::updatePseudoTimestep(BoutReal previous_timestep,
14151519 throw BoutException (" SNESSolver::updatePseudoTimestep invalid BoutPTCStrategy" );
14161520}
14171521
1418- PetscErrorCode SNESSolver::rhs_function (Vec x, Vec f, bool linear) {
1419- // Get data from PETSc into BOUT++ fields
1522+ PetscErrorCode SNESSolver::toPhysicalState (Vec x, Vec physical_x) {
14201523 if (scale_vars) {
1421- // scaled_x <- x * var_scaling_factors
1422- PetscCall (VecPointwiseMult (scaled_x, x, var_scaling_factors));
1423- } else if (asinh_vars) {
1424- PetscCall (VecCopy (x, scaled_x));
1524+ PetscCall (VecPointwiseMult (physical_x, x, var_scaling_factors));
14251525 } else {
1426- scaled_x = x ;
1526+ PetscCall ( VecCopy (x, physical_x)) ;
14271527 }
14281528
14291529 if (asinh_vars) {
14301530 PetscInt size;
1431- PetscCall (VecGetLocalSize (scaled_x , &size));
1531+ PetscCall (VecGetLocalSize (physical_x , &size));
14321532
1433- BoutReal* scaled_data = nullptr ;
1434- PetscCall (VecGetArray (scaled_x , &scaled_data ));
1533+ BoutReal* physical_data = nullptr ;
1534+ PetscCall (VecGetArray (physical_x , &physical_data ));
14351535 for (PetscInt i = 0 ; i != size; ++i) {
1436- scaled_data [i] = asinh_scale * std::sinh (scaled_data [i]);
1536+ physical_data [i] = asinh_scale * std::sinh (physical_data [i]);
14371537 }
1438- PetscCall (VecRestoreArray (scaled_x , &scaled_data ));
1538+ PetscCall (VecRestoreArray (physical_x , &physical_data ));
14391539 }
14401540
1541+ return PETSC_SUCCESS ;
1542+ }
1543+
1544+ PetscErrorCode SNESSolver::raw_rhs_function (Vec x, Vec f, bool linear) {
14411545 const BoutReal* xdata = nullptr ;
1442- PetscCall (VecGetArrayRead (scaled_x , &xdata));
1546+ PetscCall (VecGetArrayRead (x , &xdata));
14431547 // const_cast needed due to load_vars API. Not writing to xdata.
14441548 load_vars (const_cast <BoutReal*>(xdata));
1445- PetscCall (VecRestoreArrayRead (scaled_x , &xdata));
1549+ PetscCall (VecRestoreArrayRead (x , &xdata));
14461550
14471551 try {
14481552 // Call RHS function
@@ -1460,6 +1564,18 @@ PetscErrorCode SNESSolver::rhs_function(Vec x, Vec f, bool linear) {
14601564 BoutReal* fdata = nullptr ;
14611565 PetscCall (VecGetArray (f, &fdata));
14621566 save_derivs (fdata);
1567+ PetscCall (VecRestoreArray (f, &fdata));
1568+
1569+ return PETSC_SUCCESS ;
1570+ }
1571+
1572+ PetscErrorCode SNESSolver::scaled_rhs_function (Vec x, Vec f, bool linear) {
1573+ if (!scale_vars && !asinh_vars) {
1574+ return raw_rhs_function (x, f, linear);
1575+ }
1576+
1577+ PetscCall (toPhysicalState (x, scaled_x));
1578+ PetscCall (raw_rhs_function (scaled_x, f, linear));
14631579
14641580 if (asinh_vars) {
14651581 // Modify time-derivatives for asinh(var) using chain rule
@@ -1472,14 +1588,15 @@ PetscErrorCode SNESSolver::rhs_function(Vec x, Vec f, bool linear) {
14721588 PetscCall (VecGetLocalSize (f, &size));
14731589 const BoutReal* scaled_data = nullptr ;
14741590 PetscCall (VecGetArrayRead (scaled_x, &scaled_data));
1591+ BoutReal* fdata = nullptr ;
1592+ PetscCall (VecGetArray (f, &fdata));
14751593 for (PetscInt i = 0 ; i != size; ++i) {
14761594 fdata[i] /= std::sqrt (SQ (scaled_data[i]) + SQ (asinh_scale));
14771595 }
1596+ PetscCall (VecRestoreArray (f, &fdata));
14781597 PetscCall (VecRestoreArrayRead (scaled_x, &scaled_data));
14791598 }
14801599
1481- PetscCall (VecRestoreArray (f, &fdata));
1482-
14831600 if (scale_vars) {
14841601 PetscCall (VecPointwiseDivide (f, f, var_scaling_factors));
14851602 }
@@ -1490,7 +1607,7 @@ PetscErrorCode SNESSolver::rhs_function(Vec x, Vec f, bool linear) {
14901607PetscErrorCode SNESSolver::snes_function (Vec x, Vec f, bool linear) {
14911608
14921609 // Call the RHS function
1493- if (rhs_function (x, f, linear) != PETSC_SUCCESS ) {
1610+ if (scaled_rhs_function (x, f, linear) != PETSC_SUCCESS ) {
14941611 // Tell SNES that the input was out of domain
14951612 SNESSetFunctionDomainError (snes);
14961613 // Note: Returning non-zero error here leaves vectors in locked state
@@ -1664,7 +1781,20 @@ PetscErrorCode ComputeJacobianScaledColor(SNES snes, Vec x1, Mat Jac, Mat Jac_ne
16641781 CHKERRQ (err);
16651782
16661783 // Call the SNESSolver function
1667- return fctx->scaleJacobian (Jac_new);
1784+ PetscCall (fctx->scaleJacobian (Jac_new));
1785+ PetscFunctionReturn (fctx->maybeExportJacobian (Jac_new, x1));
1786+ }
1787+
1788+ PetscErrorCode ComputeJacobianDefaultMaybeExport (SNES snes, Vec x1, Mat Jac, Mat Jac_new,
1789+ void * ctx) {
1790+ PetscErrorCode err = SNESComputeJacobianDefault (snes, x1, Jac, Jac_new, ctx);
1791+ CHKERRQ (err);
1792+
1793+ if ((err != 0 ) or (ctx == nullptr )) {
1794+ return err;
1795+ }
1796+
1797+ PetscFunctionReturn (static_cast <SNESSolver*>(ctx)->maybeExportJacobian (Jac_new, x1));
16681798}
16691799} // namespace
16701800
0 commit comments