Skip to content

Commit 495d7d5

Browse files
authored
Merge pull request #2889 from boutproject/pvcode-cvode-improvements
Track field operations for debugging
2 parents 49bb02d + 3f31ab6 commit 495d7d5

File tree

16 files changed

+398
-23
lines changed

16 files changed

+398
-23
lines changed

.git-blame-ignore-revs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ ed2117e6d6826a98b6988e2f18c0c34e408563b6
55
17ac13c28aa3b34a0e46dbe87bb3874f6b25e706
66
# Added by the bot
77
4b010b7634aee1045743be80c268d4644522cd29
8+
52301380586fdbf890f620c04f689b08d89a6c34
89
a71cad2dd6ace5741a754e2ca7daacd4bb094e0e
10+
83cf77923a4c72e44303354923021acf932b4fd2
911
2c2402ed59c91164eaff46dee0f79386b7347e9e
1012
05b7c571544c3bcb153fce67d12b9ac48947fc2d

include/bout/field3d.hxx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,15 @@ class Field3D;
3333
#include "bout/field2d.hxx"
3434
#include "bout/fieldperp.hxx"
3535
#include "bout/region.hxx"
36+
#include "bout/traits.hxx"
3637

38+
#include <memory>
3739
#include <optional>
40+
#include <string>
3841
#include <vector>
3942

4043
class Mesh;
44+
class Options;
4145

4246
/// Class for 3D X-Y-Z scalar fields
4347
/*!
@@ -291,6 +295,17 @@ public:
291295
/// cuts on closed field lines?
292296
bool requiresTwistShift(bool twist_shift_enabled);
293297

298+
/// Enable a special tracking mode for debugging
299+
/// Save all changes that, are done to the field, to tracking
300+
Field3D& enableTracking(const std::string& name, std::weak_ptr<Options> tracking);
301+
302+
/// Disable tracking
303+
Field3D& disableTracking() {
304+
tracking.reset();
305+
tracking_state = 0;
306+
return *this;
307+
}
308+
294309
/////////////////////////////////////////////////////////
295310
// Data access
296311

@@ -493,6 +508,8 @@ public:
493508

494509
int size() const override { return nx * ny * nz; };
495510

511+
std::weak_ptr<Options> getTracking() { return tracking; };
512+
496513
private:
497514
/// Array sizes (from fieldmesh). These are valid only if fieldmesh is not null
498515
int nx{-1}, ny{-1}, nz{-1};
@@ -508,6 +525,22 @@ private:
508525

509526
/// RegionID over which the field is valid
510527
std::optional<size_t> regionID;
528+
529+
/// counter for tracking, to assign unique names to the variable names
530+
int tracking_state{0};
531+
std::weak_ptr<Options> tracking;
532+
// name is changed if we assign to the variable, while selfname is a
533+
// non-changing copy that is used for the variable names in the dump files
534+
std::string selfname;
535+
template <typename T>
536+
void track(const T& change, const std::string& operation) {
537+
if (tracking_state != 0) {
538+
_track(change, operation);
539+
}
540+
}
541+
template <typename T, typename = bout::utils::EnableIfField<T>>
542+
void _track(const T& change, std::string operation);
543+
void _track(const BoutReal& change, std::string operation);
511544
};
512545

513546
// Non-member overloaded operators

include/bout/options.hxx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,9 @@ auto Options::as(const Tensor<int>& similar_to) const -> Tensor<int>;
10121012
/// Convert \p value to string
10131013
std::string toString(const Options& value);
10141014

1015+
/// Save the parallel fields
1016+
void saveParallel(Options& opt, const std::string& name, const Field3D& tosave);
1017+
10151018
/// Output a stringified \p value to a stream
10161019
///
10171020
/// This is templated to avoid implict casting: anything is

include/bout/options_io.hxx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,36 @@
55
///
66
/// 1. Dump files, containing time history:
77
///
8-
/// auto dump = OptionsIOFactory::getInstance().createOutput();
9-
/// dump->write(data);
8+
/// auto dump = OptionsIOFactory::getInstance().createOutput();
9+
/// dump->write(data);
1010
///
1111
/// where data is an Options tree. By default dump files are configured
1212
/// with the root `output` section, or an Option tree can be passed to
1313
/// `createOutput`.
1414
///
1515
/// 2. Restart files:
1616
///
17-
/// auto restart = OptionsIOFactory::getInstance().createOutput();
18-
/// restart->write(data);
17+
/// auto restart = OptionsIOFactory::getInstance().createRestart();
18+
/// restart->write(data);
1919
///
2020
/// where data is an Options tree. By default restart files are configured
2121
/// with the root `restart_files` section, or an Option tree can be passed to
2222
/// `createRestart`.
2323
///
2424
/// 3. Ad-hoc single files
25-
/// Note: The caller should consider how multiple processors interact with the file.
25+
/// Note: The caller should consider how multiple processors interact with the file.
2626
///
27-
/// auto file = OptionsIOFactory::getInstance().createFile("some_file.nc");
28-
/// or
29-
/// auto file = OptionsIO::create("some_file.nc");
27+
/// auto file = OptionsIOFactory::getInstance().createFile("some_file.nc");
28+
/// or
29+
/// auto file = OptionsIO::create("some_file.nc");
3030
///
31+
/// 4. Ad-hoc parallel files
32+
/// This adds also metric information, such that the file can be read with
33+
/// boutdata or xBOUT
34+
///
35+
/// OptionIO::write("some_file", data, mesh);
36+
///
37+
/// if mesh is omitted, no grid information is added.
3138
///
3239

3340
#pragma once
@@ -37,6 +44,7 @@
3744

3845
#include "bout/build_defines.hxx"
3946
#include "bout/generic_factory.hxx"
47+
#include "bout/mesh.hxx"
4048
#include "bout/options.hxx"
4149

4250
#include <memory>
@@ -77,6 +85,11 @@ public:
7785
/// This uses the default file type and default options.
7886
static std::unique_ptr<OptionsIO> create(const std::string& file);
7987

88+
/// Write some data to a file with a given name prefix
89+
/// This will be done in parallel. If Mesh is given, also mesh data will be
90+
/// added, which is needed for xBOUT or boutdata to read the files.
91+
static void write(const std::string& prefix, Options& data, Mesh* mesh = nullptr);
92+
8093
/// Create an OptionsIO for I/O to the given file.
8194
/// The file will be configured using the given `config` options:
8295
/// - "type" : string The file type e.g. "netcdf" or "adios"

manual/sphinx/developer_docs/debugging.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,30 @@ If you need to capture runtime information in the message, you can use
158158
the ``fmt`` syntax also used by the loggers::
159159

160160
TRACE("Value of i={}, some arbitrary {}", i, "string");
161+
162+
163+
Time evolution
164+
==============
165+
166+
It can be useful to know what happened when the simulation failed. The pvode
167+
solver can dump the state of the simulation, at the time the solver
168+
failed. This information includes the individual terms in the derivative. This
169+
allows to identify which term is causing the issue. Additionally, the
170+
residuum is dumped. This identifies not only which term is causing the issue,
171+
but also where in the domain the solver is struggling. This can be enabled
172+
with::
173+
174+
solver:type=pvode solver:debug_on_failure=true
175+
176+
It can be also useful for understanding why the solver is slow. Forcing a
177+
higher min_timestep, the solver will fail to evolve the system as it
178+
encounters the situation, and provides information where it is happening.
179+
This can be done with::
180+
181+
solver:type=pvode solver:debug_on_failure=true solver:min_timestep=1e2
182+
183+
It is also possible to dump at a specific time using the euler solver.
184+
This can be useful for tracking down what is causing differences between two
185+
different versions. It can be used with::
186+
187+
solver:type=euler solver:dump_at_time=0 input:error_on_unused_options=false

manual/sphinx/user_docs/bout_options.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ Fields can also be stored and written::
889889
Options fields;
890890
fields["f2d"] = Field2D(1.0);
891891
fields["f3d"] = Field3D(2.0);
892-
bout::OptionsIO::create("fields.nc").write(fields);
892+
bout::OptionsIO::create("fields.nc")->write(fields);
893893

894894
This allows the input settings and evolving variables to be
895895
combined into a single tree (see above on joining trees) and written

src/field/field3d.cxx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
#include <bout/globals.hxx>
3232

3333
#include <cmath>
34+
#include <cpptrace/cpptrace.hpp>
35+
#include <memory>
36+
#include <utility>
3437

3538
#include "bout/parallel_boundary_op.hxx"
3639
#include "bout/parallel_boundary_region.hxx"
@@ -46,6 +49,8 @@
4649
#include <bout/output.hxx>
4750
#include <bout/utils.hxx>
4851

52+
#include "fmt/format.h"
53+
4954
/// Constructor
5055
Field3D::Field3D(Mesh* localmesh, CELL_LOC location_in, DirectionTypes directions_in)
5156
: Field(localmesh, location_in, directions_in) {
@@ -235,6 +240,8 @@ Field3D& Field3D::operator=(const Field3D& rhs) {
235240
return (*this); // skip this assignment
236241
}
237242

243+
track(rhs, "operator=");
244+
238245
// Copy base slice
239246
Field::operator=(rhs);
240247

@@ -254,6 +261,7 @@ Field3D& Field3D::operator=(const Field3D& rhs) {
254261
}
255262

256263
Field3D& Field3D::operator=(Field3D&& rhs) {
264+
track(rhs, "operator=");
257265

258266
// Move parallel slices or delete existing ones.
259267
yup_fields = std::move(rhs.yup_fields);
@@ -274,6 +282,7 @@ Field3D& Field3D::operator=(Field3D&& rhs) {
274282
}
275283

276284
Field3D& Field3D::operator=(const Field2D& rhs) {
285+
track(rhs, "operator=");
277286

278287
/// Check that the data is allocated
279288
ASSERT1(rhs.isAllocated());
@@ -318,6 +327,7 @@ void Field3D::operator=(const FieldPerp& rhs) {
318327
}
319328

320329
Field3D& Field3D::operator=(const BoutReal val) {
330+
track(val, "operator=");
321331

322332
// Delete existing parallel slices. We don't copy parallel slices, so any
323333
// that currently exist will be incorrect.
@@ -831,3 +841,63 @@ Field3D::getValidRegionWithDefault(const std::string& region_name) const {
831841
void Field3D::setRegion(const std::string& region_name) {
832842
regionID = fieldmesh->getRegionID(region_name);
833843
}
844+
845+
Field3D& Field3D::enableTracking(const std::string& name,
846+
std::weak_ptr<Options> _tracking) {
847+
tracking = std::move(_tracking);
848+
tracking_state = 1;
849+
selfname = name;
850+
return *this;
851+
}
852+
853+
template <typename T, typename>
854+
void Field3D::_track(const T& change, std::string operation) {
855+
if (tracking_state == 0) {
856+
return;
857+
}
858+
auto locked = tracking.lock();
859+
if (locked == nullptr) {
860+
return;
861+
}
862+
const std::string outname{fmt::format("track_{:s}_{:d}", selfname, tracking_state++)};
863+
864+
locked->set(outname, change, "tracking");
865+
866+
const std::string trace = cpptrace::generate_trace().to_string();
867+
868+
// Workaround for bug in gcc9.4
869+
#if BOUT_USE_TRACK
870+
const std::string changename = change.name;
871+
#endif
872+
(*locked)[outname].setAttributes({
873+
{"operation", operation},
874+
#if BOUT_USE_TRACK
875+
{"rhs.name", changename},
876+
#endif
877+
{"trace", trace},
878+
});
879+
}
880+
881+
template void
882+
Field3D::_track<Field3D, bout::utils::EnableIfField<Field3D>>(const Field3D&,
883+
std::string);
884+
template void Field3D::_track<Field2D>(const Field2D&, std::string);
885+
template void Field3D::_track<>(const FieldPerp&, std::string);
886+
887+
void Field3D::_track(const BoutReal& change, std::string operation) {
888+
if (tracking_state == 0) {
889+
return;
890+
}
891+
auto locked = tracking.lock();
892+
if (locked == nullptr) {
893+
return;
894+
}
895+
const std::string trace = cpptrace::generate_trace().to_string();
896+
const std::string outname{fmt::format("track_{:s}_{:d}", selfname, tracking_state++)};
897+
locked->set(outname, change, "tracking");
898+
(*locked)[outname].setAttributes({
899+
{"operation", operation},
900+
{"rhs.name", "BoutReal"},
901+
{"trace", trace},
902+
});
903+
}

src/field/gen_fieldops.jinja

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,16 @@
129129
}
130130
{% endif %}
131131

132+
{% if lhs == "Field3D" %}
133+
track(rhs, "operator{{operator}}=");
134+
{% endif %}
135+
132136
checkData(*this);
133137

134138
} else {
139+
{% if lhs == "Field3D" %}
140+
track(rhs, "operator{{operator}}=");
141+
{% endif %}
135142
(*this) = (*this) {{operator}} {{rhs.name}};
136143
}
137144
return *this;

0 commit comments

Comments
 (0)