Nonuniform BoundaryConditions - #1179
Conversation
* Dirichlet * Neumann * Free * for 2nd, 3rd and 4th each
Indexing starts at 0, but zero offset means that we are starting inside the boundary. Thus we need the +1 to only take points inside the domain.
|
Definitely agree there is an issue here. I think from a skim of your code here that the solution you implement is taking into account explicitly the positions of each point in x-y space. That, if I follow these lecture notes correctly --- https://www.nada.kth.se/kurser/kth/2D1263/l6.pdf [see section 4.3, in particular eqs (4.2), (4.4) and figs 3.3 and 3.5-6] --- is the most accurate thing to do, but our derivative operators don't do that, they just use the local grid spacing at the location of the derivative (updating the non-uniform grid support in the derivatives could be a useful thing to do...?). Using the local spacing at the boundary is a bit of a pain, especially with staggered fields (they end up needing the grid spacing at the x-y corner of cells to put the x-boundary condition on a y-staggered field or vice versa). I have put together some changes, building on other stuff I was doing to get difops to converge accurately near boundaries, that get the grid spacings at the boundary locations. See the derivatives3 test in e8b28bb, which is updated to have a non-constant grid spacing and to apply boundary conditions. With the updates, it converges correctly for dirichlet, neumann and free boundary conditions, on staggered and unstaggered grids. The cost compared to your approach is having to change a lot more stuff in Coordinates, essentially to get a |
|
Some timings: - based on 41382ba Only I thought of implementing some adaptive stencils - but haven't came around to do it. I thought of doing it as an aiolos-only feature, as the standard stencil code does not know the position of the derivative. |
This reverts commit ef008b2.
|
This is ready to be merged - can someone have a look over this? As this is to my understanding the correct implementation, thus we could discuss to replace the old, non-uniform mesh aware implementation, but I thought as a first step we could have it in parallel, to allow testing ... |
ZedThree
left a comment
There was a problem hiding this comment.
Looking good. I'd like to get this in so we can base the boundary rewrite on it.
| return data[d.ind]; | ||
| } | ||
|
|
||
| BoutReal& operator[](const Indices &d) { |
There was a problem hiding this comment.
I'd like to avoid bringing these operators back if possible. If we added something like
// const Mesh& would be better, but I think Mesh const-ness might need fixing
Ind3D indexFromXYZ(int x, int y, int z, Mesh* mesh)would that avoid the need for Indices? If not, then I would suggest just moving Indices entirely into boundary_nonuniform.cxx and just making it POD:
struct Indices {
int x, y, z;
};and explicitly indexing the fields with f(i.x, i.y, i.z)
There was a problem hiding this comment.
I like Indices because they allow better readability, and are also not worse then integer indices performance wise. Further they allow to write code for Fields. Finally reintroducing them removes a breaking change from the 4.2(?) minor release.
I did not understand why they were removed in the first place, could you explain?
There was a problem hiding this comment.
We removed them because we don't want to have to maintain many different ways of indexing fields. Also Indices was really an implementation detail of DataIterator. Now that we have improved on DataIterator it doesn't make sense to keep Indices about as well.
| } else { | ||
| stag = 1; | ||
| } | ||
| } |
There was a problem hiding this comment.
These two conditionals are repeated in each apply. Can they be pulled out into free function(s)?
|
It would be really good if this included the tests as well. Can they be written without using |
|
@dschwoerer could you add a docstring explaining how the coefficients are calculated? I'd find that very helpful. |
|
Do you mean the derivation, starting with the taylor expansion, or rather explain the implementation? |
|
If you could sketch the derivation, with notation corresponding to the variables in the code, that would be great 🥇 |
The test seems to be broken, as the convergence is not what it is supposed to be. This is most likely a bug in getting the correct dy and realy, which seems to limit the accuracy. with the realy() implementation, the test converged to more reasonable results, i.e. an o4 was never limited to first order convergence.
Do not generate code by default. The inversions take around 3 minutes to compute.
The differences are not that drastic that the code cannot be unified again.
| Field3D f{0.}; | ||
| f.setBoundary("f"); | ||
| Options* opt = Options::getRoot(); | ||
| int ntests; |
There was a problem hiding this comment.
warning: variable 'ntests' is not initialized [cppcoreguidelines-init-variables]
| int ntests; | |
| int ntests = 0; |
| add(new BoundaryFree_O2(), "free_o2"); | ||
| add(new BoundaryFree_O3(), "free_o3"); | ||
|
|
||
| add(new BoundaryDirichletNonUniform_O4(), "dirichlet_nu_o4"); |
There was a problem hiding this comment.
warning: initializing non-owner argument of type 'BoundaryOp *' with a newly created 'gsl::owner<>' [cppcoreguidelines-owning-memory]
add(new BoundaryDirichletNonUniform_O4(), "dirichlet_nu_o4");
^|
|
||
| #if !BOUT_USE_METRIC_3D | ||
|
|
||
| void BoundaryDirichletNonUniform_O2::apply(Field3D& f, MAYBE_UNUSED(BoutReal t)) { |
There was a problem hiding this comment.
warning: function 'apply' has cognitive complexity of 39 (threshold 25) [readability-function-cognitive-complexity]
void BoundaryDirichletNonUniform_O2::apply(Field3D& f, MAYBE_UNUSED(BoutReal t)) {
^src/mesh/boundary_nonuniform.cxx:51: +1, including nesting penalty of 0, nesting level increased to 1
if (!fg)
^src/mesh/boundary_nonuniform.cxx:62: +1, including nesting penalty of 0, nesting level increased to 1
for (; !bndry->isDone(); bndry->next1d()) {
^src/mesh/boundary_nonuniform.cxx:63: +2, including nesting penalty of 1, nesting level increased to 2
if (fg) {
^src/mesh/boundary_nonuniform.cxx:75: +3, including nesting penalty of 2, nesting level increased to 3
for (int zk = 0; zk < mesh->LocalNz; zk++) {
^src/mesh/boundary_nonuniform.cxx:84: +2, including nesting penalty of 1, nesting level increased to 2
bndry->by != 0 ? mesh->getCoordinates()->dy : mesh->getCoordinates()->dx;
^src/mesh/boundary_nonuniform.cxx:86: +2, including nesting penalty of 1, nesting level increased to 2
if (stagger == 0) {
^src/mesh/boundary_nonuniform.cxx:93: +1, nesting level increased to 2
} else {
^src/mesh/boundary_nonuniform.cxx:97: +2, including nesting penalty of 1, nesting level increased to 2
if (stagger == -1) {
^src/mesh/boundary_nonuniform.cxx:102: +2, including nesting penalty of 1, nesting level increased to 2
for (int i = ((stagger == -1) ? -1 : 0); i < bndry->width; i++) {
^src/mesh/boundary_nonuniform.cxx:102: +3, including nesting penalty of 2, nesting level increased to 3
for (int i = ((stagger == -1) ? -1 : 0); i < bndry->width; i++) {
^src/mesh/boundary_nonuniform.cxx:105: +3, including nesting penalty of 2, nesting level increased to 3
if (stagger == 0) {
^src/mesh/boundary_nonuniform.cxx:110: +1, nesting level increased to 3
} else {
^src/mesh/boundary_nonuniform.cxx:111: +4, including nesting penalty of 3, nesting level increased to 4
if (stagger == -1 && i != -1) {
^src/mesh/boundary_nonuniform.cxx:111: +1
if (stagger == -1 && i != -1) {
^src/mesh/boundary_nonuniform.cxx:115: +4, including nesting penalty of 3, nesting level increased to 4
if (stagger == 1) {
^src/mesh/boundary_nonuniform.cxx:119: +3, including nesting penalty of 2, nesting level increased to 3
for (int iz = 0; iz < mesh->LocalNz; iz++) {
^src/mesh/boundary_nonuniform.cxx:120: +4, including nesting penalty of 3, nesting level increased to 4
const BoutReal val = (fg) ? vals[iz] : 0.0;
^| if (!fg) | ||
| fg = f.getBndryGenerator(bndry->location); |
There was a problem hiding this comment.
warning: statement should be inside braces [readability-braces-around-statements]
| if (!fg) | |
| fg = f.getBndryGenerator(bndry->location); | |
| if (!fg) { | |
| fg = f.getBndryGenerator(bndry->location); | |
| } |
| } | ||
| } | ||
|
|
||
| vec2 spacing; |
There was a problem hiding this comment.
warning: uninitialized record type: 'spacing' [cppcoreguidelines-pro-type-member-init]
| vec2 spacing; | |
| vec2 spacing{}; |
| bndry->by != 0 ? mesh->getCoordinates()->dy : mesh->getCoordinates()->dx; | ||
| Indices i1{bndry->x - 1 * bndry->bx, bndry->y - 1 * bndry->by, 0}; | ||
| if (stagger == 0) { | ||
| BoutReal offset; |
There was a problem hiding this comment.
warning: variable 'offset' is not initialized [cppcoreguidelines-init-variables]
src/mesh/boundary_nonuniform.cxx:0:
- #include <bout/constants.hxx>
+ #include <math.h>
+
+ #include <bout/constants.hxx>| BoutReal offset; | |
| BoutReal offset = NAN; |
| BoutReal total_offset = 0; | ||
| offset = coords_field(i1.x, i1.y); | ||
| spacing.f1 = total_offset + offset / 2; | ||
| total_offset += offset; |
There was a problem hiding this comment.
warning: Value stored to 'total_offset' is never read [clang-analyzer-deadcode.DeadStores]
total_offset += offset;
^src/mesh/boundary_nonuniform.cxx:92: Value stored to 'total_offset' is never read
total_offset += offset;
^Also use SpecificInd for data access
This avoids race conditions, as the rename in the end is atomic. Out-of-source generation doesn't help, as the copy in the end is not atomic and rename cannot be used in general.
|
|
||
| #if !BOUT_USE_METRIC_3D | ||
| using IndMetric = Ind2D; | ||
| #define IND(var, x, y, z) IndMetric var(x* localNy + y, localNy, 1) |
There was a problem hiding this comment.
warning: function-like macro 'IND' used; consider a 'constexpr' template function [cppcoreguidelines-macro-usage]
#define IND(var, x, y, z) IndMetric var(x* localNy + y, localNy, 1)
^There was a problem hiding this comment.
I could use a jinja2 macros instead of a C macros. Would that be preferred? Certainly clang-tidy would be happy ...
|
|
||
| #if !BOUT_USE_METRIC_3D | ||
| using IndMetric = Ind2D; | ||
| #define IND(var, x, y, z) IndMetric var(x* localNy + y, localNy, 1) |
There was a problem hiding this comment.
warning: macro argument should be enclosed in parentheses [bugprone-macro-parentheses]
| #define IND(var, x, y, z) IndMetric var(x* localNy + y, localNy, 1) | |
| #define IND(var, x, y, z) IndMetric var((x)* localNy + y, localNy, 1) |
|
|
||
| #if !BOUT_USE_METRIC_3D | ||
| using IndMetric = Ind2D; | ||
| #define IND(var, x, y, z) IndMetric var(x* localNy + y, localNy, 1) |
There was a problem hiding this comment.
warning: macro argument should be enclosed in parentheses [bugprone-macro-parentheses]
| #define IND(var, x, y, z) IndMetric var(x* localNy + y, localNy, 1) | |
| #define IND(var, x, y, z) IndMetric var(x* localNy + (y), localNy, 1) |
| #if !BOUT_USE_METRIC_3D | ||
| using IndMetric = Ind2D; | ||
| #define IND(var, x, y, z) IndMetric var(x* localNy + y, localNy, 1) | ||
| #define IND3D(var, x, y, z) Ind3D var((x * localNy + y) * localNz + z, localNy, localNz) |
There was a problem hiding this comment.
warning: function-like macro 'IND3D' used; consider a 'constexpr' template function [cppcoreguidelines-macro-usage]
#define IND3D(var, x, y, z) Ind3D var((x * localNy + y) * localNz + z, localNy, localNz)
^| #if !BOUT_USE_METRIC_3D | ||
| using IndMetric = Ind2D; | ||
| #define IND(var, x, y, z) IndMetric var(x* localNy + y, localNy, 1) | ||
| #define IND3D(var, x, y, z) Ind3D var((x * localNy + y) * localNz + z, localNy, localNz) |
There was a problem hiding this comment.
warning: macro argument should be enclosed in parentheses [bugprone-macro-parentheses]
| #define IND3D(var, x, y, z) Ind3D var((x * localNy + y) * localNz + z, localNy, localNz) | |
| #define IND3D(var, x, y, z) Ind3D var(((x) * localNy + y) * localNz + z, localNy, localNz) |
|
|
||
| for (; !bndry->isDone(); bndry->next1d()) { | ||
|
|
||
| vec2 spacing; |
There was a problem hiding this comment.
warning: uninitialized record type: 'spacing' [cppcoreguidelines-pro-type-member-init]
| vec2 spacing; | |
| vec2 spacing{}; |
| #else | ||
| Ind3D ic{temp3d + Ind3D(i * offset * localNz)}; | ||
| #endif | ||
| vec2 facs; |
There was a problem hiding this comment.
warning: uninitialized record type: 'facs' [cppcoreguidelines-pro-type-member-init]
| vec2 facs; | |
| vec2 facs{}; |
| #if BOUT_USE_METRIC_3D | ||
| #define MAKE3D(x) x | ||
| #else | ||
| #define MAKE3D(x) x##3d |
There was a problem hiding this comment.
warning: function-like macro 'MAKE3D' used; consider a 'constexpr' template function [cppcoreguidelines-macro-usage]
#define MAKE3D(x) x##3d
^| return new BoundaryFreeNonUniform_O2(region, newgen); | ||
| } | ||
|
|
||
| vec2 BoundaryFreeNonUniform_O2::calc_interp_to_stencil(const vec2& spacing) const { |
There was a problem hiding this comment.
warning: method 'calc_interp_to_stencil' can be made static [readability-convert-member-functions-to-static]
| vec2 BoundaryFreeNonUniform_O2::calc_interp_to_stencil(const vec2& spacing) const { | |
| vec2 BoundaryFreeNonUniform_O2::calc_interp_to_stencil(const vec2& spacing) { |
src/mesh/boundary_nonuniform.hxx:92:
- vec2 calc_interp_to_stencil(const vec2& spacing) const;
+ static vec2 calc_interp_to_stencil(const vec2& spacing) ;| } | ||
|
|
||
| vec2 BoundaryFreeNonUniform_O2::calc_interp_to_stencil(const vec2& spacing) const { | ||
| vec2 facs; |
There was a problem hiding this comment.
warning: uninitialized record type: 'facs' [cppcoreguidelines-pro-type-member-init]
| vec2 facs; | |
| vec2 facs{}; |
| return facs; | ||
| } | ||
|
|
||
| void BoundaryDirichletNonUniform_O3::apply(Field3D & f, MAYBE_UNUSED(BoutReal t)) { |
There was a problem hiding this comment.
warning: function 'apply' has cognitive complexity of 39 (threshold 25) [readability-function-cognitive-complexity]
void BoundaryDirichletNonUniform_O3::apply(Field3D & f, MAYBE_UNUSED(BoutReal t)) {
^src/mesh/boundary_nonuniform.cxx:446: +1, including nesting penalty of 0, nesting level increased to 1
if (!fg) {
^src/mesh/boundary_nonuniform.cxx:458: +1, including nesting penalty of 0, nesting level increased to 1
for (; !bndry->isDone(); bndry->next1d()) {
^src/mesh/boundary_nonuniform.cxx:459: +2, including nesting penalty of 1, nesting level increased to 2
if (fg) {
^src/mesh/boundary_nonuniform.cxx:471: +3, including nesting penalty of 2, nesting level increased to 3
for (int zk = 0; zk < mesh->LocalNz; zk++) {
^src/mesh/boundary_nonuniform.cxx:480: +2, including nesting penalty of 1, nesting level increased to 2
bndry->by != 0 ? mesh->getCoordinates()->dy : mesh->getCoordinates()->dx;
^src/mesh/boundary_nonuniform.cxx:505: +2, including nesting penalty of 1, nesting level increased to 2
if (stagger == 0) {
^src/mesh/boundary_nonuniform.cxx:513: +1, nesting level increased to 2
} else {
^src/mesh/boundary_nonuniform.cxx:518: +2, including nesting penalty of 1, nesting level increased to 2
if (stagger == -1) {
^src/mesh/boundary_nonuniform.cxx:533: +2, including nesting penalty of 1, nesting level increased to 2
for (int i = ((stagger == -1) ? -1 : 0); i < bndry->width; i++) {
^src/mesh/boundary_nonuniform.cxx:533: +3, including nesting penalty of 2, nesting level increased to 3
for (int i = ((stagger == -1) ? -1 : 0); i < bndry->width; i++) {
^src/mesh/boundary_nonuniform.cxx:542: +3, including nesting penalty of 2, nesting level increased to 3
if (stagger == 0) {
^src/mesh/boundary_nonuniform.cxx:547: +1, nesting level increased to 3
} else {
^src/mesh/boundary_nonuniform.cxx:548: +4, including nesting penalty of 3, nesting level increased to 4
if (stagger == -1 && i != -1) {
^src/mesh/boundary_nonuniform.cxx:548: +1
if (stagger == -1 && i != -1) {
^src/mesh/boundary_nonuniform.cxx:552: +4, including nesting penalty of 3, nesting level increased to 4
if (stagger == 1) {
^src/mesh/boundary_nonuniform.cxx:557: +3, including nesting penalty of 2, nesting level increased to 3
for (int iz{0}; iz < mesh->LocalNz; iz++) {
^src/mesh/boundary_nonuniform.cxx:564: +4, including nesting penalty of 3, nesting level increased to 4
const BoutReal val = (fg) ? vals[iz] : 0.0;
^| } | ||
| } | ||
|
|
||
| vec3 spacing; |
There was a problem hiding this comment.
warning: uninitialized record type: 'spacing' [cppcoreguidelines-pro-type-member-init]
| vec3 spacing; | |
| vec3 spacing{}; |
| #else | ||
| Ind3D ic{temp3d + Ind3D(i * offset * localNz)}; | ||
| #endif | ||
| vec3 facs; |
There was a problem hiding this comment.
warning: uninitialized record type: 'facs' [cppcoreguidelines-pro-type-member-init]
| vec3 facs; | |
| vec3 facs{}; |
| #if BOUT_USE_METRIC_3D | ||
| #define MAKE3D(x) x | ||
| #else | ||
| #define MAKE3D(x) x##3d |
There was a problem hiding this comment.
warning: function-like macro 'MAKE3D' used; consider a 'constexpr' template function [cppcoreguidelines-macro-usage]
#define MAKE3D(x) x##3d
^| return new BoundaryDirichletNonUniform_O3(region, newgen); | ||
| } | ||
|
|
||
| vec3 BoundaryDirichletNonUniform_O3::calc_interp_to_stencil(const vec3& spacing) |
There was a problem hiding this comment.
warning: method 'calc_interp_to_stencil' can be made static [readability-convert-member-functions-to-static]
src/mesh/boundary_nonuniform.cxx:584:
- const {
+ {src/mesh/boundary_nonuniform.hxx:111:
- vec3 calc_interp_to_stencil(const vec3& spacing) const;
+ static vec3 calc_interp_to_stencil(const vec3& spacing) ;| } | ||
| } | ||
|
|
||
| vec4 spacing; |
There was a problem hiding this comment.
warning: uninitialized record type: 'spacing' [cppcoreguidelines-pro-type-member-init]
| vec4 spacing; | |
| vec4 spacing{}; |
| #else | ||
| Ind3D ic{temp3d + Ind3D(i * offset * localNz)}; | ||
| #endif | ||
| vec4 facs; |
There was a problem hiding this comment.
warning: uninitialized record type: 'facs' [cppcoreguidelines-pro-type-member-init]
| vec4 facs; | |
| vec4 facs{}; |
| #if BOUT_USE_METRIC_3D | ||
| #define MAKE3D(x) x | ||
| #else | ||
| #define MAKE3D(x) x##3d |
There was a problem hiding this comment.
warning: function-like macro 'MAKE3D' used; consider a 'constexpr' template function [cppcoreguidelines-macro-usage]
#define MAKE3D(x) x##3d
^| return new BoundaryDirichletNonUniform_O4(region, newgen); | ||
| } | ||
|
|
||
| vec4 BoundaryDirichletNonUniform_O4::calc_interp_to_stencil( |
There was a problem hiding this comment.
warning: method 'calc_interp_to_stencil' can be made static [readability-convert-member-functions-to-static]
src/mesh/boundary_nonuniform.cxx:1038:
- const vec4& spacing) const {
+ const vec4& spacing) {src/mesh/boundary_nonuniform.hxx:168:
- vec4 calc_interp_to_stencil(const vec4& spacing) const;
+ static vec4 calc_interp_to_stencil(const vec4& spacing) ;|
|
||
| vec4 BoundaryDirichletNonUniform_O4::calc_interp_to_stencil( | ||
| const vec4& spacing) const { | ||
| vec4 facs; |
There was a problem hiding this comment.
warning: uninitialized record type: 'facs' [cppcoreguidelines-pro-type-member-init]
| vec4 facs; | |
| vec4 facs{}; |
Makes the C++ code simpler. The template does not change a lot, as the conditionals just change from C++ to jinja.
So for 2D and large enough |
| } | ||
| } | ||
|
|
||
| vec2 spacing; |
There was a problem hiding this comment.
warning: uninitialized record type: 'spacing' [cppcoreguidelines-pro-type-member-init]
| vec2 spacing; | |
| vec2 spacing{}; |
There was a problem hiding this comment.
I would like to avoid the overhead of that ... (applies to all copies of the uninitialised warnings)
| #if BOUT_USE_METRIC_3D | ||
| #define MAKE3D(x) x | ||
| #else | ||
| #define MAKE3D(x) x##3d |
There was a problem hiding this comment.
warning: function-like macro 'MAKE3D' used; consider a 'constexpr' template function [cppcoreguidelines-macro-usage]
#define MAKE3D(x) x##3d
^| #if BOUT_USE_METRIC_3D | ||
| #define MAKE3D(x) x | ||
| #else | ||
| #define MAKE3D(x) x##3d |
There was a problem hiding this comment.
warning: function-like macro 'MAKE3D' used; consider a 'constexpr' template function [cppcoreguidelines-macro-usage]
#define MAKE3D(x) x##3d
^| #if BOUT_USE_METRIC_3D | ||
| #define MAKE3D(x) x | ||
| #else | ||
| #define MAKE3D(x) x##3d |
There was a problem hiding this comment.
warning: function-like macro 'MAKE3D' used; consider a 'constexpr' template function [cppcoreguidelines-macro-usage]
#define MAKE3D(x) x##3d
^| #if BOUT_USE_METRIC_3D | ||
| #define MAKE3D(x) x | ||
| #else | ||
| #define MAKE3D(x) x##3d |
There was a problem hiding this comment.
warning: function-like macro 'MAKE3D' used; consider a 'constexpr' template function [cppcoreguidelines-macro-usage]
#define MAKE3D(x) x##3d
^| #if BOUT_USE_METRIC_3D | ||
| #define MAKE3D(x) x | ||
| #else | ||
| #define MAKE3D(x) x##3d |
There was a problem hiding this comment.
warning: function-like macro 'MAKE3D' used; consider a 'constexpr' template function [cppcoreguidelines-macro-usage]
#define MAKE3D(x) x##3d
^There was a problem hiding this comment.
I doubt that can be replaced with a template function ...
Is it possible?
There was a problem hiding this comment.
I guess something like this should work:
struct Make3D {
const FieldMetric& fm;
#if BOUT_USE_METRIC_3D
const Field3D& f3d;
#else
const Field3D f3d;
#endif
Make3D(const FieldMetric& f) : fm(f), f3d(f) {};
}
|
This is, as written, currently not compatible with 3D metrics. Fixing it requires some time, which I currently do not have. |
Add boundaries which are aware of non-uniform grid spacing:
Tests are not included, as they requirerealyThe code uses sympy to generate the C++ code, so extend should be easy.