Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pvcode + Cvode improvements #2889

Open
wants to merge 31 commits into
base: next
Choose a base branch
from
Open

Conversation

dschwoerer
Copy link
Contributor

  • Exposes more options to the user.
  • Allows to dump if the solver fails. This can be useful for figuring out why the solver is slow. Currently only for pvode, but should also be possible to implement this for cvode (for cvode we can use a documented API)

Use the new track feature (better name required) to dump the different
components of the ddt() as well as the residuum for the evolved fields.
This keeps track of all the changes done to the field and stores them to
a OptionsObject.
This keeps track of all the changes done to the field and stores them to
a OptionsObject.
@@ -683,4 +683,12 @@ inline T floor(const T& var, BoutReal f, const std::string& rgn = "RGN_ALL") {

#undef FIELD_FUNC

template <typename T, typename = bout::utils::EnableIfField<T>, class... Types>
inline T setName(T&& f, const std::string& name, Types... args) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think most other setters like this are member functions

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make this a member function, make it virtual:

field.hxx:

virtual Field& setName(std::string name) {
   self->name = std::move(name);
   return *self;
}

field2d.hxx:

Field2D& setName(std::string name) override {
   Field::setName(name);
   return *self;
}

Formatting will have to be done at the calling site, but I think that's fine

@ZedThree
Copy link
Member

My worry here is the tracking storing a lot of data if it's storing the full Field3D on every single change. Should it also be a compile-time option so that it completely disappears for production runs?

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

include/bout/field3d.hxx Outdated Show resolved Hide resolved
include/bout/field3d.hxx Outdated Show resolved Hide resolved
include/bout/field3d.hxx Outdated Show resolved Hide resolved
src/field/field3d.cxx Outdated Show resolved Hide resolved
src/field/field3d.cxx Outdated Show resolved Hide resolved
src/solver/impls/pvode/pvode.cxx Outdated Show resolved Hide resolved
}
pvode_load_data_f3d(evolve_bndrys, ffs,
prefix == "pre_"s ? udata : N_VDATA(cv_mem->cv_acor));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: variable name 'f' is too short, expected at least 3 characters [readability-identifier-length]

      for (auto& f : f3d) {
                 ^

f.F_var->enableTracking(fmt::format("ddt_{:s}", f.name), debug);
setName(*f.var, f.name);
}
run_rhs(simtime);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: variable name 'f' is too short, expected at least 3 characters [readability-identifier-length]

      for (auto& f : f3d) {
                 ^

src/solver/impls/pvode/pvode.cxx Outdated Show resolved Hide resolved
src/solver/impls/pvode/pvode.cxx Outdated Show resolved Hide resolved
@dschwoerer
Copy link
Contributor Author

My worry here is the tracking storing a lot of data if it's storing the full Field3D on every single change. Should it also be a compile-time option so that it completely disappears for production runs?

It could be, but I don't think it is needed. I would personally keep it enabled for production runs, as it has a runtime switch, that is disabled by default. And I would also keep it like this, at least by default.

If a simulation crashes, it can be very useful to immediately get why it failed (this is currently the only use of the name tracking). Having to restart + queue again, just to get to the point where it crashes can be very time consuming, and the cost should be really negligable, if disabled.

I did notice a slow down in my simulations with this enabled, but that was when it was enabled all the time and the names did not get reset, thus the names have been growing exponentially.

@dschwoerer
Copy link
Contributor Author

dschwoerer commented Mar 19, 2024 via email

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

src/solver/impls/pvode/pvode.cxx Show resolved Hide resolved
src/solver/impls/pvode/pvode.cxx Show resolved Hide resolved
@@ -293,6 +353,49 @@
// Check return flag
if (flag != SUCCESS) {
output_error.write("ERROR CVODE step failed, flag = {:d}\n", flag);
CVodeMemRec* cv_mem = (CVodeMem)cvode_mem;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: do not use C-style cast to convert between unrelated types [cppcoreguidelines-pro-type-cstyle-cast]

    CVodeMemRec* cv_mem = (CVodeMem)cvode_mem;
                          ^

for (const auto& prefix : {"pre_"s, "residuum_"s}) {
std::vector<Field3D> list_of_fields{};
std::vector<bool> evolve_bndrys{};
for (const auto& f : f3d) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: variable name 'f' is too short, expected at least 3 characters [readability-identifier-length]

        for (const auto& f : f3d) {
                         ^

prefix == "pre_"s ? udata : N_VDATA(cv_mem->cv_acor));
}

for (auto& f : f3d) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: variable name 'f' is too short, expected at least 3 characters [readability-identifier-length]

      for (auto& f : f3d) {
                 ^

}
run_rhs(simtime);

for (auto& f : f3d) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: variable name 'f' is too short, expected at least 3 characters [readability-identifier-length]

      for (auto& f : f3d) {
                 ^

@dschwoerer
Copy link
Contributor Author

The cuda build fails with:

/__w/BOUT-dev/BOUT-dev/src/field/field3d.cxx: In member function 'Options* Field3D::track(const T&, std::string)':
/__w/BOUT-dev/BOUT-dev/src/field/field3d.cxx:851:63: error: expected ';' before '}' token
  851 |     (*tracking)[outname].setAttributes({
      |                                                               ^
      |                                                               ;
/__w/BOUT-dev/BOUT-dev/src/field/field3d.cxx:851:64: error: expected primary-expression before ',' token
  851 |     (*tracking)[outname].setAttributes({
      |                                                                ^
/__w/BOUT-dev/BOUT-dev/src/field/field3d.cxx:851:66: error: expected primary-expression before '{' token
  851 |     (*tracking)[outname].setAttributes({
      |                                                                  ^
/__w/BOUT-dev/BOUT-dev/src/field/field3d.cxx:851:95: error: invalid use of void expression
  851 |     (*tracking)[outname].setAttributes({
      |                                                                                               ^
[ 16%] Building CUDA object CMakeFiles/bout++.dir/src/field/field_data.cxx.o

The relevant code is:

    (*tracking)[outname].setAttributes({
      {"operation", operation},
#if BOUT_USE_TRACK
      {"rhs.name", change.name},
#endif
    });

Is gcc9.4 not able to use setAttributes?

@ZedThree
Copy link
Member

It could be, but I don't think it is needed. I would personally keep it enabled for production runs, as it has a runtime switch, that is disabled by default. And I would also keep it like this, at least by default.

We do try to completely remove debugging features for production runs, see TRACE for example. Being able to remove the function call entirely at lower CHECK levels would make me much happier about this.

If a simulation crashes, it can be very useful to immediately get why it failed (this is currently the only use of the name tracking). Having to restart + queue again, just to get to the point where it crashes can be very time consuming, and the cost should be really negligable, if disabled.

This is true, but I would really like to understand the costs. It looks like it's storing potentially hundreds or thousands of copies of full fields, so although it looks like a really interesting and useful feature, it would be good to have a good idea of the costs.

I did notice a slow down in my simulations with this enabled, but that was when it was enabled all the time and the names did not get reset, thus the names have been growing exponentially.

Is that with the current form?

But then they return either Field, or need to be overwritten.

It should almost certainly return Field& and then this wouldn't be an issue

@dschwoerer
Copy link
Contributor Author

It could be, but I don't think it is needed. I would personally keep it enabled for production runs, as it has a runtime switch, that is disabled by default. And I would also keep it like this, at least by default.

We do try to completely remove debugging features for production runs, see TRACE for example. Being able to remove the function call entirely at lower CHECK levels would make me much happier about this.

If at all, I would be in favor of adding a new configure flag. I use this in runs with CHECK=0.

If a simulation crashes, it can be very useful to immediately get why it failed (this is currently the only use of the name tracking). Having to restart + queue again, just to get to the point where it crashes can be very time consuming, and the cost should be really negligable, if disabled.

This is true, but I would really like to understand the costs. It looks like it's storing potentially hundreds or thousands of copies of full fields, so although it looks like a really interesting and useful feature, it would be good to have a good idea of the costs.

It is only storing fields, if the solver has failed. Unless that happens, no additional storage is used.
There are some calls, but they are all no-ops, if you think that might be a problem, I can certainly guard them with #if statements, but I think that should be orthogonal to any of the flags we have.

In the case the solver crashes, I think the cost is really negligible. This is only done once, if the solver decided it cannot continue. For hermes-2, the dump file contains 53 Field3Ds, while the normal dump file contains 45 Field3Ds.
And part of that is just the metric, so I guess the overhead is less then 100% of the storage requirement of the run.

But if BOUT++ runs out of memory here, it would not be much worse then if not - the simulation finishes anyway.

I did notice a slow down in my simulations with this enabled, but that was when it was enabled all the time and the names did not get reset, thus the names have been growing exponentially.

Is that with the current form?

No, currently it is not noticeable. I activate it for all my runs.

But then they return either Field, or need to be overwritten.

It should almost certainly return Field& and then this wouldn't be an issue

That is exactly what I tried:

diff --git a/include/bout/field.hxx b/include/bout/field.hxx
index 04035f5b7..488aecaab 100644
--- a/include/bout/field.hxx
+++ b/include/bout/field.hxx
@@ -84,6 +84,14 @@ public:
     return *this;
   }
 
+  template <class... Types>
+  inline Field& setName(const std::string& name, Types... args) {
+#if BOUT_USE_TRACK
+    this->name = fmt::format(name, args...);
+#endif
+    return *this;
+  }
+
   std::string name;
 
 #if CHECK > 0
@@ -683,19 +691,4 @@ inline T floor(const T& var, BoutReal f, const std::string& rgn = "RGN_ALL") {
 
 #undef FIELD_FUNC
 
-template <typename T, typename = bout::utils::EnableIfField<T>, class... Types>
-inline void setName(T& f, const std::string& name, Types... args) {
-#if BOUT_USE_TRACK
-  f.name = fmt::format(name, args...);
-#endif
-}
-
-template <typename T, typename = bout::utils::EnableIfField<T>, class... Types>
-inline T setName(T&& f, const std::string& name, Types... args) {
-#if BOUT_USE_TRACK
-  f.name = fmt::format(name, args...);
-#endif
-  return f;
-}
-
 #endif /* FIELD_H */
diff --git a/src/mesh/coordinates.cxx b/src/mesh/coordinates.cxx
index 32774d622..643e148b1 100644
--- a/src/mesh/coordinates.cxx
+++ b/src/mesh/coordinates.cxx
@@ -1542,7 +1542,7 @@ Field3D Coordinates::Grad_par(const Field3D& var, CELL_LOC outloc,
   TRACE("Coordinates::Grad_par( Field3D )");
   ASSERT1(location == outloc || outloc == CELL_DEFAULT);
 
-  return setName(::DDY(var, outloc, method) * invSg(), "Grad_par({:s})", var.name);
+  return (::DDY(var, outloc, method) * invSg()).setName("Grad_par({:s})", var.name);
 }
 
 /////////////////////////////////////////////////////////
@@ -1601,7 +1601,7 @@ Field3D Coordinates::Div_par(const Field3D& f, CELL_LOC outloc,
     f_B.yup(i) = f.yup(i) / Bxy_floc.yup(i);
     f_B.ydown(i) = f.ydown(i) / Bxy_floc.ydown(i);
   }
-  return setName(Bxy * Grad_par(f_B, outloc, method), "Div_par({:s})", f.name);
+  return (Bxy * Grad_par(f_B, outloc, method)).setName("Div_par({:s})", f.name);
 }
 
 /////////////////////////////////////////////////////////
diff --git a/src/solver/impls/pvode/pvode.cxx b/src/solver/impls/pvode/pvode.cxx
index db28f64d8..c36985ae0 100644
--- a/src/solver/impls/pvode/pvode.cxx
+++ b/src/solver/impls/pvode/pvode.cxx
@@ -376,7 +376,7 @@ BoutReal PvodeSolver::run(BoutReal tout) {
 
       for (auto& f : f3d) {
         f.F_var->enableTracking(fmt::format("ddt_{:s}", f.name), debug);
-        setName(*f.var, f.name);
+        f.var->setName(f.name);
       }
       run_rhs(simtime);

gcc14 complains with:

/u/dave/soft/BOUT-dev/merge/src/mesh/coordinates.cxx: In member function ‘Field3D Coordinates::Grad_par(const Field3D&, CELL_LOC, const std::string&)’:                                                                                                                       /u/dave/soft/BOUT-dev/merge/src/mesh/coordinates.cxx:1545:56: error: could not convert ‘operator*(const Field3D&, const Field2D&)((* &((Coordinates*)this)->Coordinates::invSg())).Field3D::Field.Field::setName<std::__cxx11::basic_string<char, std::char_traits<char>, std:
:allocator<char> > >(std::__cxx11::basic_string<char>(((const char*)"Grad_par({:s})"), std::allocator<char>()), std::__cxx11::basic_string<char>(var.Field3D::Field.Field::name))’ from ‘Field’ to ‘Field3D’
 1545 |   return (::DDY(var, outloc, method) * invSg()).setName("Grad_par({:s})", var.name);      
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~
      |                                                        |
      |                                                        Field                       
/u/dave/soft/BOUT-dev/merge/src/mesh/coordinates.cxx: In member function ‘Field3D Coordinates::Div_par(const Field3D&, CELL_LOC, const std::string&)’:
/u/dave/soft/BOUT-dev/merge/src/mesh/coordinates.cxx:1604:55: error: could not convert ‘operator*(const Field2D&, const Field3D&)(Coordinates::Grad_par(const Field3D&, CELL_LOC, const std::string&)(f_B, outloc, (* & method))).Field3D::Field.Field::setName<std::__cxx11::
basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::__cxx11::basic_string<char>(((const char*)"Div_par({:s})"), std::allocator<char>()), std::__cxx11::basic_string<char>(f.Field3D::Field.Field::name))’ from ‘Field’ to ‘Field3D’
 1604 |   return (Bxy * Grad_par(f_B, outloc, method)).setName("Div_par({:s})", f.name);                                                                                                                                                                                            |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~
      |                                                       |
      |                                                       Field

gcc 9.4 is unable to correctly parse the construction for the function
argument, if name.change is used directly. First making a copy seems to work
around that issue.
@ZedThree
Copy link
Member

ZedThree commented Apr 8, 2024

@dschwoerer After discussing this with @bendudson, we're quite concerned about the potential overhead of the temporary field dumping mechanism, both in terms of memory and performance, for what seems like a small benefit when debugging. We'd really like to see some performance analysis and scaling to see it doesn't have an affect when disabled. I'm also worried that we'd need to see setName everywhere in the code for this to be really useful.

Could something similar not be achieved with a custom monitor instead?

Exposing more CVODE options to the user seems very straightforward and useful, so maybe that could be split out?

@dschwoerer
Copy link
Contributor Author

@dschwoerer After discussing this with @bendudson, we're quite concerned about the potential overhead of the temporary field dumping mechanism, both in terms of memory and performance, for what seems like a small benefit when debugging.

But the memory overhead is only if the solver fails. Do you still think at that point that is an issue?
I know of @totork using BOUT++ in a inner-loop of an optimization script, where just failing vs crashing would matter. Thus having an option to disable would make sense.

We'd really like to see some performance analysis and scaling to see it doesn't have an affect when disabled.

I can certainly run blob2d for this branch and next. But I am certain there are no significant differences, thus I haven't done it. Would you like to see something else? Would just the BOUT++ internal timings sufficient? Would you like mpi runs?

I'm also worried that we'd need to see setName everywhere in the code for this to be really useful.

Sure, having them makes things more easily readable. But that is optional, and I can certainly add some if you think this is worth merging an wanted before merging. It would make BOUT_USE_TRACK also more useful. Right now

Could something similar not be achieved with a custom monitor instead?

I would not know how. If you can explain how to, I am happy to change the design.

Exposing more CVODE options to the user seems very straightforward and useful, so maybe that could be split out?

I can split them out 👍

Comment on lines +250 to +253
debug_on_failure =
(*options)["debug_on_failure"]
.doc("Run an aditional rhs if the solver fails with extra tracking")
.withDefault(false);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is now disabled by default, and needs to be enabled with solver:debug_on_failure=true

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

src/solver/impls/euler/euler.cxx Show resolved Hide resolved
src/solver/impls/euler/euler.cxx Show resolved Hide resolved
src/solver/impls/euler/euler.cxx Show resolved Hide resolved
src/solver/impls/pvode/pvode.cxx Show resolved Hide resolved
src/solver/impls/pvode/pvode.cxx Show resolved Hide resolved
src/solver/impls/pvode/pvode.cxx Show resolved Hide resolved
@ZedThree
Copy link
Member

Ok, I think we understand this a bit better now. I'd still be much happier if this was a macro and only turned on for CHECK > 0. I'm quite worried about this proliferating everywhere and performance dying a death by a thousand cuts.

@dschwoerer
Copy link
Contributor Author

Ok, I think we understand this a bit better now. I'd still be much happier if this was a macro and only turned on for CHECK > 0. I'm quite worried about this proliferating everywhere and performance dying a death by a thousand cuts.

That is a fair point. I do understand that worry, but at the same time I am convinced it is orthogonal to CHECK=0.
I am happy to add a new configure option. -DBOUT_EXTRA_DEBUG_ON_FAILURE ? I guess it make sense to have it disabled by default, as unless you know about the option and will look at the debug files, it is rather useless.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

src/field/field3d.cxx Show resolved Hide resolved
Options* Field3D::track(const BoutReal& change, std::string operation) {
if (tracking and tracking_state) {
const std::string outname{fmt::format("track_{:s}_{:d}", selfname, tracking_state++)};
tracking->set(outname, change, "tracking");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: implicit conversion 'Options *' -> bool [readability-implicit-bool-conversion]

Suggested change
tracking->set(outname, change, "tracking");
if ((tracking != nullptr) and tracking_state) {

Options* Field3D::track(const BoutReal& change, std::string operation) {
if (tracking and tracking_state) {
const std::string outname{fmt::format("track_{:s}_{:d}", selfname, tracking_state++)};
tracking->set(outname, change, "tracking");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: implicit conversion 'int' -> bool [readability-implicit-bool-conversion]

Suggested change
tracking->set(outname, change, "tracking");
if (tracking and (tracking_state != 0)) {

Options& debug = *debug_ptr;
Mesh* mesh{nullptr};
for (auto& f : f3d) {
saveParallel(debug, f.name, *f.var);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use of undeclared identifier 'saveParallel' [clang-diagnostic-error]

      saveParallel(debug, f.name, *f.var);
      ^

@@ -293,6 +358,57 @@ BoutReal PvodeSolver::run(BoutReal tout) {
// Check return flag
if (flag != SUCCESS) {
output_error.write("ERROR CVODE step failed, flag = {:d}\n", flag);
if (debug_on_failure) {
CVodeMemRec* cv_mem = (CVodeMem)cvode_mem;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: do not use C-style cast to convert between unrelated types [cppcoreguidelines-pro-type-cstyle-cast]

      CVodeMemRec* cv_mem = (CVodeMem)cvode_mem;
                            ^

for (auto& f : f3d) {
debug[f.name] = *f.var;
if (f.var->hasParallelSlices()) {
saveParallel(debug, f.name, *f.var);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use of undeclared identifier 'saveParallel' [clang-diagnostic-error]

	    saveParallel(debug, f.name, *f.var);
     ^

dschwoerer and others added 3 commits October 22, 2024 12:05
Just dumping the parallel slices does in general not work, as then
collect discards that, especially if NYPE==ny
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

@@ -949,6 +949,9 @@ Tensor<BoutReal> Options::as<Tensor<BoutReal>>(const Tensor<BoutReal>& similar_t
/// Convert \p value to string
std::string toString(const Options& value);

/// Save the parallel fields
void saveParallel(Options& opt, const std::string name, const Field3D& tosave);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: parameter 'name' is const-qualified in the function declaration; const-qualification of parameters only has an effect in function definitions [readability-avoid-const-params-in-decls]

Suggested change
void saveParallel(Options& opt, const std::string name, const Field3D& tosave);
void saveParallel(Options& opt, std::string name, const Field3D& tosave);

@@ -337,6 +337,22 @@ void Options::assign<>(Tensor<BoutReal> val, std::string source) {
_set_no_check(std::move(val), std::move(source));
}

void saveParallel(Options& opt, const std::string name, const Field3D& tosave) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: the const qualified parameter 'name' is copied for each invocation; consider making it a reference [performance-unnecessary-value-param]

Suggested change
void saveParallel(Options& opt, const std::string name, const Field3D& tosave) {
void saveParallel(Options& opt, const std::string& name, const Field3D& tosave) {

include/bout/options.hxx:952:

- void saveParallel(Options& opt, const std::string name, const Field3D& tosave);
+ void saveParallel(Options& opt, const std::string& name, const Field3D& tosave);

src/sys/options.cxx Outdated Show resolved Hide resolved
src/sys/options.cxx Outdated Show resolved Hide resolved
src/sys/options.cxx Show resolved Hide resolved
src/sys/options.cxx Show resolved Hide resolved
src/sys/options.cxx Show resolved Hide resolved
@dschwoerer
Copy link
Contributor Author

@ZedThree anything I can do to move this forward?

Copy link
Member

@ZedThree ZedThree left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still a bit wary of this because it feels like this whole other system that needs to be everywhere for it to be useful, and the current implementation is a nice demonstration of it for a single, limited purpose.

If we're going to have it and start using it, then we definitely need some docs in the debugging section, more docstrings in the code, a bit more polish on the API, and some tests.

I'd also still really like to see some proper benchmarks comparing next and this branch.


int tracking_state{0};
Options* tracking{nullptr};
std::string selfname;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this if we already have name?

Comment on lines +535 to +536
Options* track(const T& change, std::string operation);
Options* track(const BoutReal& change, std::string operation);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value seems to never be used -- just return void?

int tracking_state{0};
Options* tracking{nullptr};
std::string selfname;
template <class T>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we only provide implementations for some T, but this could also have EnableIfField<T> to completely remove it as an option, rather than having a link-time error?

Actually, does this even need to be a template? Could just take const Field&?

@@ -265,6 +266,7 @@ Field3D& Field3D::operator=(const Field3D& rhs) {

Field3D& Field3D::operator=(Field3D&& rhs) {
TRACE("Field3D: Assignment from Field3D");
track(rhs, "operator=");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really think this should be a macro that is only enabled at higher CHECK

@@ -683,4 +683,12 @@ inline T floor(const T& var, BoutReal f, const std::string& rgn = "RGN_ALL") {

#undef FIELD_FUNC

template <typename T, typename = bout::utils::EnableIfField<T>, class... Types>
inline T setName(T&& f, const std::string& name, Types... args) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make this a member function, make it virtual:

field.hxx:

virtual Field& setName(std::string name) {
   self->name = std::move(name);
   return *self;
}

field2d.hxx:

Field2D& setName(std::string name) override {
   Field::setName(name);
   return *self;
}

Formatting will have to be done at the calling site, but I think that's fine

@@ -949,6 +949,9 @@ Tensor<BoutReal> Options::as<Tensor<BoutReal>>(const Tensor<BoutReal>& similar_t
/// Convert \p value to string
std::string toString(const Options& value);

/// Save the parallel fields
void saveParallel(Options& opt, const std::string name, const Field3D& tosave);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be a method rather than a free function? Then it can be called like options[name].assignParallel(value)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think that works, as we assign to name as well as f"{name}_y+1" etc ...

Comment on lines +64 to +65
std::vector<bool>::const_iterator evolve_bndry = evolve_bndrys.begin();
for (std::vector<Field3D>::iterator ff = ffs.begin(); ff != ffs.end(); ++ff) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not this?

Suggested change
std::vector<bool>::const_iterator evolve_bndry = evolve_bndrys.begin();
for (std::vector<Field3D>::iterator ff = ffs.begin(); ff != ffs.end(); ++ff) {
auto evolve_bndry = evolve_bndrys.cbegin();
for (auto& ff : ffs) {

run_rhs(simtime);

for (auto& f : f3d) {
debug[f.name] = *f.var;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

saveParallel already includes this

Comment on lines +408 to +410
} else {
output_warn.write("debug_on_failure is currently only supported for Field3Ds");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invert the conditional and move this first, so we can immediately return and then unindent the rest of the block

@@ -514,6 +527,13 @@ private:

/// RegionID over which the field is valid
std::optional<size_t> regionID;

int tracking_state{0};
Options* tracking{nullptr};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like storing raw pointers. Probably this makes most sense as weak_ptr, a non-owning reference to a shared_ptr allocated and owned elsewhere. That way it's very obvious we don't own it and we can rely on it living long enough

@ZedThree
Copy link
Member

ZedThree commented Nov 7, 2024

Exposing the additional PVODE options is just obvious and should probably just be it's own PR as well

@mikekryjak
Copy link
Contributor

@ZedThree just wanted to chime in and say that having this capability available would be extremely useful for me in optimising Hermes-3. I had already played with it quite a bit, but unfortunately I had to put it on the back burner for now because of the DLS paper and other work. I fully intend to do proper testing of this to optimise Hermes-3 performance sometime in Jan-Feb, or earlier if Michael Hardman can have a go.

I understand that there could be performance concerns - just wanted to say that this is much more than a small debugging benefit for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants