Skip to content

Commit 1a60d3f

Browse files
committed
SNES solver: Save Jacobian to file
- PetscPreconditioner saves Jacobian using PETSc binary or ASCII format. - Solver saves metadata: A Jacobian global index offset field to the dmp files, and a JSON file with the variable information. - SNES solver outputs Jacobian if `save_jacobian = true`. An option `jacobian_export_kind` select whether the Jacobian is calculated using the nonlinear system being solved (that depends on timestep and scaling), the scaled RHS function (that depends on variable scaling), or the raw `rhs` function is saved.
1 parent b445c7e commit 1a60d3f

6 files changed

Lines changed: 381 additions & 30 deletions

File tree

include/bout/petsc_preconditioner.hxx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010
#ifndef BOUT_PETSC_PRECONDITIONER_H
1111
#define BOUT_PETSC_PRECONDITIONER_H
1212

13+
#include "bout/bout_enum_class.hxx"
1314
#include "bout/build_defines.hxx"
1415

16+
BOUT_ENUM_CLASS(PetscMatrixExportFormat, binary, ascii);
17+
1518
#if BOUT_HAS_PETSC
1619

1720
#include "bout/petsc_interface.hxx"
@@ -22,6 +25,8 @@
2225
#include <petscmat.h>
2326
#include <petscvec.h>
2427

28+
#include <string>
29+
2530
class Options;
2631
class Field3D;
2732

@@ -73,6 +78,13 @@ public:
7378
Mat jacobian() const { return Jfd; }
7479
MatFDColoring coloring() const { return fdcoloring; }
7580

81+
static PetscErrorCode
82+
saveMatrix(Mat matrix, const std::string& filename,
83+
PetscMatrixExportFormat format = PetscMatrixExportFormat::binary);
84+
PetscErrorCode
85+
saveMatrix(const std::string& filename,
86+
PetscMatrixExportFormat format = PetscMatrixExportFormat::binary) const;
87+
7688
void reset();
7789

7890
private:
@@ -86,6 +98,9 @@ private:
8698
// unconditionally in PETSc-enabled compilation units.
8799
class PetscPreconditioner {
88100
public:
101+
void saveMatrix(
102+
const std::string& UNUSED(filename),
103+
PetscMatrixExportFormat UNUSED(format) = PetscMatrixExportFormat::binary) const {}
89104
void reset() {}
90105
};
91106

include/bout/solver.hxx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838

3939
#include "bout/build_defines.hxx"
4040

41+
#include "bout/bout_enum_class.hxx"
4142
#include "bout/bout_types.hxx"
4243
#include "bout/boutexception.hxx"
4344
#include "bout/globals.hxx"
@@ -101,6 +102,8 @@ constexpr auto SOLVERRKGENERIC = "rkgeneric";
101102
enum class FieldCategories : std::uint8_t { VARS, DERIVS, MMS };
102103
enum class SOLVER_VAR_OP : std::uint8_t { LOAD, SET_ID, SAVE };
103104

105+
BOUT_ENUM_CLASS(JacobianExportKind, system, scaled, rhs);
106+
104107
/// A type to set where in the list monitors are added
105108
enum class MonitorPosition { BACK, FRONT };
106109

@@ -368,6 +371,25 @@ public:
368371
protected:
369372
friend class SundialsNVectorInterface;
370373

374+
struct JacobianVariableMetadata {
375+
int offset{0};
376+
std::string name;
377+
std::string location;
378+
bool evolve_bndry{false};
379+
bool constraint{false};
380+
std::string description;
381+
};
382+
383+
struct JacobianMetadata {
384+
int format_version{1};
385+
std::string solver_name;
386+
int n2d{0};
387+
int n3d{0};
388+
std::vector<JacobianVariableMetadata> variables_2d;
389+
std::vector<JacobianVariableMetadata> variables_3d;
390+
std::string ordering;
391+
};
392+
371393
/// Number of command-line arguments
372394
static int* pargc;
373395
/// Command-line arguments
@@ -606,6 +628,12 @@ protected:
606628

607629
/// Returns a Field3D containing the global indices
608630
Field3D globalIndex(int localStart);
631+
Field3D jacobianIndexBase(int localStart = 0);
632+
std::vector<JacobianVariableMetadata> getJacobianMetadata2D() const;
633+
std::vector<JacobianVariableMetadata> getJacobianMetadata3D() const;
634+
JacobianMetadata getJacobianMetadata(const std::string& solver_name) const;
635+
void writeJacobianMetadataJson(const std::string& filename,
636+
const std::string& solver_name) const;
609637

610638
/// Maximum internal timestep
611639
BoutReal max_dt{-1.0};
@@ -670,6 +698,7 @@ private:
670698
std::string run_restart_from = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";
671699
/// Save `run_id` and `run_restart_from` every output
672700
bool save_repeat_run_id{false};
701+
bool save_jacobian_index_base{false};
673702

674703
/// Current iteration (output time-step) number
675704
int iteration{0};

src/solver/impls/snes/snes.cxx

Lines changed: 153 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
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+
6474
PetscErrorCode 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

7282
PetscErrorCode 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

7688
PetscErrorCode 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

369473
int 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, &current_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, &current_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) {
14901607
PetscErrorCode 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

Comments
 (0)