Skip to content

Add region arguments for toFieldAligned/fromFieldAligned - #1432

Merged
ZedThree merged 23 commits into
nextfrom
region-arguments-tofromFieldAligned
Feb 21, 2019
Merged

Add region arguments for toFieldAligned/fromFieldAligned#1432
ZedThree merged 23 commits into
nextfrom
region-arguments-tofromFieldAligned

Conversation

@johnomotani

@johnomotani johnomotani commented Dec 10, 2018

Copy link
Copy Markdown
Contributor

Add region arguments (where it is valid to give RGN_NOX or RGN_NOBNDRY) so functions like toFieldAligned/fromFieldAligned can be used both when y-guard cells have been set and when they have not. e.g. when using fromFieldAligned on the results of interpolation or derivatives, y-guard cells cannot be included, but when using toFieldAligned on the inputs to interpolation or derivatives, y-guard cells must be included.

Pass RGN_NOBNDRY to fromFieldAligned in various places where the guard cells aren't valid.

Pulled out of #1176; is independent of other stuff in that PR, which now probably won't be merged.

Add region arguments (where it is valid to give RGN_NOX or RGN_NOBNDRY)
so functions like toFieldAligned/fromFieldAligned can be used both when
y-guard cells have been set and when they have not. e.g. when using
fromFieldAligned on the results of interpolation or derivatives, y-guard
cells cannot be included, but when using toFieldAligned on the inputs to
interpolation or derivatives, y-guard cells must be included.
Comment thread src/mesh/parallel/shiftedmetric.cxx Outdated
const Field3D ShiftedMetric::shiftZ(const Field3D &f, const Field2D &zangle) {
const Field3D ShiftedMetric::shiftZ(const Field3D &f, const Field2D &zangle, const REGION region) {
ASSERT1(&mesh == f.getMesh());
ASSERT1(region == RGN_NOX || region == RGN_NOBNDRY); // Never calculate x-guard cells here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not sure this is necessary; it may be inefficient to perform shifting in the X guard cells, but shouldn't cause a problem. There are some cases where it may be useful e.g. calculating D2DXDY

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think the problem I was originally trying to avoid was when shifting in the corner guard cells. I remember getting 'uninitialized value' warnings from Valgrind that I wanted to protect against. They must have been coming from interpolated zShift fields though, so probably the right solution is to make sure all points in zShifts get initialized to something even when checking is turned off, instead of trying to force the corners to not be used here.

I'll get rid of this check 👍

Following discussion in PR #1432: it is not necessary to restrict the
regions for to/fromFieldAligned or shiftZ to RGN_NOX and RGN_NOBNDRY as
was being done previously.
Comment thread src/mesh/parallel/shiftedmetric.cxx Outdated
shiftZ(f(jx,jy), phs[jx][jy], result(jx,jy));
}

for (const auto &i : mesh.getRegion2D(REGION_STRING(region))) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could this use the BOUT_FOR(_SERIAL )macro rather than the range based loop?

Comment thread src/mesh/parallel/shiftedmetric.cxx Outdated
// (Note valgrind complains about corner guard cells if we try to loop over
// the whole grid, because zShift is not initialized in the corner guard
// cells.)
for(const auto &i : mesh.getRegion2D(REGION_STRING(region))) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

BOUT_FOR?

Comment thread src/mesh/parallel/shiftedmetric.cxx Outdated
BOUT_FOR(i, zShift.getRegion("RGN_ALL")) {
for(int jz=0;jz<nmodes;jz++) {
BoutReal kwave=jz*2.0*PI/zlength; // wave number is 1/[rad]
fromAlignedPhs[i.x()][i.y()][jz] = dcomplex(cos(kwave*zShift(i.x(),i.y())) , -sin(kwave*zShift(i.x(),i.y())));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think you can just index zShift using i (i.e. zShift[i]) here.

Comment thread src/mesh/parallel/shiftedmetric.cxx Outdated
for(int jz=0;jz<nmodes;jz++) {
BoutReal kwave=jz*2.0*PI/zlength; // wave number is 1/[rad]
BOUT_FOR(i, zShift.getRegion("RGN_ALL")) {
BoutReal yupShift = zShift(i.x(),i.y()) - zShift(i.x(),i.y()+1);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Index with i and i.yp() possible here I think.

Comment thread src/mesh/parallel/shiftedmetric.cxx Outdated
shiftZ(&(f(jx,jy+1,0)), yupPhs[jx][jy], &(yup(jx,jy+1,0)));
}
BOUT_FOR(i, f.getRegion("RGN_NOX")) {
shiftZ(&(f(i.x(),i.y()+1,0)), yupPhs[i.x()][i.y()], &(yup(i.x(),i.y()+1,0)));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Again I think you can use i.yp().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@d7919: This loop should be using getRegion2D, so can't use i.yp for f. I've updated to follow the rest of your comments.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@johnomotani it's possible to index a Field3D with an Ind2D, you just need to specify the z index as well. This uses a multiply and add to get the corresponding Ind3D as opposed to three divides and a modulus to call i.x() and i.y(), as such I'd expect the Ind2D approach to be more efficient. I'll add these changes to the branch that fixes the OpenMP stuff

Comment thread src/mesh/parallel/shiftedmetric.cxx Outdated
shiftZ(&(f(jx,jy-1,0)), ydownPhs[jx][jy], &(ydown(jx,jy-1,0)));
}
BOUT_FOR(i, f.getRegion("RGN_NOX")) {
shiftZ(&(f(i.x(),i.y()-1,0)), ydownPhs[i.x()][i.y()], &(ydown(i.x(),i.y()-1,0)));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

i.ym().

Comment thread src/mesh/parallel/shiftedmetric.cxx Outdated
}

BOUT_FOR(i, f.getRegion(region)) {
shiftZ(f(i.x(),i.y()), phs[i.x()][i.y()], result(i.x(),i.y()));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you replace f(i.x(), i.y()) with &f(i.x(), i.y(), 0) and the same for result. Note I think you want f.getRegion2D(region) (or f.getMesh()->getRegion2D(region)) as currently you're looping over z as well.

Comment thread src/mesh/parallel/shiftedmetric.cxx Outdated
// the whole grid, because zShift is not initialized in the corner guard
// cells.)
BOUT_FOR(i, f.getRegion(region)) {
shiftZ(f(i.x(), i.y()), mesh.LocalNz, zangle(i.x(),i.y()), result(i.x(), i.y()));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

See previous comments.

Regions used in BOUT_FOR loops must exclude y-guard cells, like the
previous nested loops.
@johnomotani

Copy link
Copy Markdown
Contributor Author

test-yupdown is failing with OpenMP. The test passes if I change the loop in this shiftZ method from BOUT_FOR to BOUT_FOR_SERIAL

const Field3D ShiftedMetric::shiftZ(const Field3D &f, const arr3Dvec &phs, const REGION region) {
ASSERT1(&mesh == f.getMesh());
if(mesh.LocalNz == 1)
return f; // Shifting makes no difference
Field3D result(&mesh);
result.allocate();
BOUT_FOR(i, mesh.getRegion2D(REGION_STRING(region))) {
shiftZ(&f(i.x(), i.y(), 0), phs[i.x()][i.y()], &result(i.x(), i.y(), 0));
}
return result;
}

I can't see why the loop should not parallelize though. The loop body for each i should be unaffected by the result for any other i... Anyone got any hints for debugging?

@d7919

d7919 commented Dec 17, 2018

Copy link
Copy Markdown
Member

I don't have the code in front of me but done of these methods use a class member working space (vector) which would lead to race competition etc. I think this could probably be removed from class and made a routine local Array without performance penalty. Can look later if any help

@d7919

d7919 commented Dec 17, 2018

Copy link
Copy Markdown
Member

I've pushed a branch at https://github.com/boutproject/BOUT-dev/tree/region-arguments-tofromFieldAligned-shiftZ-openmp which might help fix this.

@ZedThree

Copy link
Copy Markdown
Member

This needs updating now #1345 is in

Need to set number of y-guard cells to 2 before creating default
regions. Otherwise BOUT_FOR loops go out of bounds.
@johnomotani

Copy link
Copy Markdown
Contributor Author

I've merged next now.

This PR does two things:

  1. adds and makes use of REGION arguments for the toFieldAligned fromFieldAligned methods.
  2. Uses Tensor for the cached phases in ShiftedMetric instead of arr3Dvec

I think 1 is good and should be merged. @ZedThree do you want 2, and if so should I split it into a separate PR?

RGN_ALL is no longer the default for toFieldAligned/fromFieldAligned.
Force it to be used for the unit test.
@johnomotani

Copy link
Copy Markdown
Contributor Author

This is failing with OpenMP enabled again. If I switch the BOUT_FOR to a BOUT_FOR_SERIAL in this loop, it passes

for (auto& phase : phases) {
// In C++17 std::vector::emplace_back returns a reference, which
// would be very useful here!
results.emplace_back(&mesh);
auto& current_result = results.back();
current_result.allocate();
current_result.setLocation(f.getLocation());
BOUT_FOR(i, mesh.getRegion2D("RGN_NOY")) {
// Deep copy the FFT'd field
Array<dcomplex> shifted_temp(nmodes);
for (int jz = 1; jz < nmodes; ++jz) {
shifted_temp[jz] = f_fft(i.x(), i.y() + phase.y_offset, jz)
* phase.phase_shift(i.x(), i.y(), jz);
}
irfft(shifted_temp.begin(), mesh.LocalNz,
&current_result(i.yp(phase.y_offset), 0));
}
}

Can anyone see the race condition or other error?

@d7919

d7919 commented Feb 13, 2019

Copy link
Copy Markdown
Member

Probably unrelated but

for (int jz = 1; jz < nmodes; ++jz) {

doesn't set the jz=0 point which I think we probably should as it's not initialised anywhere (this could just be shifted_temp[0] = 0 before the loop)

@d7919

d7919 commented Feb 13, 2019

Copy link
Copy Markdown
Member

The only place inside the loop that I could see an issue arising is with

&current_result(i.yp(phase.y_offset), 0));

As we're writing to current_result and this is a shared object. However, it looks like it should be fine as i.yp(phase.y_offset) should be unique for each i which is the OpenMP loop variable.

@ZedThree

ZedThree commented Feb 13, 2019

Copy link
Copy Markdown
Member

Probably unrelated but

BOUT-dev/src/mesh/parallel/shiftedmetric.cxx

Line 205 in e1857ec

for (int jz = 1; jz < nmodes; ++jz) {
doesn't set the jz=0 point which I think we probably should as it's not initialised anywhere (this could just be shifted_temp[0] = 0 before the loop)

L232-233 has been clobbered, it should look like:

Array<dcomplex> shifted_temp(f_fft[jx][jy + phase.y_offset]);
shifted_temp.ensureUnique();

@johnomotani

Copy link
Copy Markdown
Contributor Author

@ZedThree I'd changed f_fft to a Tensor so couldn't get an Array from it to initialize shifted_temp with. I thought it would be OK to move getting values out of f_fft down into the loop instead, using shifted_temp[jz] = f_fft(i.x(), i.y() + phase.y_offset, jz) * phase.phase_shift(i.x(), i.y(), jz); instead of shifted_temp[jz] *= phase.phase_shift(i.x(), i.y(), jz);.

I've just pushed a commit replacing Tensor<dcomplex> f_fft with Matrix<Array<dcomplex>> f_fft so the structure looks a lot more like it did previously. The unit tests now pass with OpenMP. I still don't understand what the problem was though... I tried adding shifted_temp.ensureUnique() and shifted_temp[0] = 0., giving the method below, but still got the same errors

std::vector<Field3D>
ShiftedMetric::shiftZ(const Field3D& f,
                      const std::vector<ParallelSlicePhase>& phases) const {

  const int nmodes = mesh.LocalNz / 2 + 1;

  // FFT in Z of input field at each (x, y) point
  Tensor<dcomplex> f_fft(mesh.LocalNx, mesh.LocalNy, nmodes);

  BOUT_FOR(i, mesh.getRegion2D("RGN_ALL")) {
    rfft(&f(i, 0), mesh.LocalNz, &f_fft(i.x(), i.y(), 0));
  }

  std::vector<Field3D> results{};

  for (auto& phase : phases) {
    // In C++17 std::vector::emplace_back returns a reference, which
    // would be very useful here!
    results.emplace_back(&mesh);
    auto& current_result = results.back();
    current_result.allocate();
    current_result.setLocation(f.getLocation());
  
    BOUT_FOR(i, mesh.getRegion2D("RGN_NOY")) {
      // Deep copy the FFT'd field
      Array<dcomplex> shifted_temp(nmodes);
      shifted_temp.ensureUnique();
    
      shifted_temp[0] = 0.;
      for (int jz = 1; jz < nmodes; ++jz) {
        shifted_temp[jz] = f_fft(i.x(), i.y() + phase.y_offset, jz)
                           * phase.phase_shift(i.x(), i.y(), jz);
      }
  
      irfft(shifted_temp.begin(), mesh.LocalNz,
            &current_result(i.yp(phase.y_offset), 0));
    }
  } 
     
  return results;
} 

Swap some array entries around so that 'input' in ShiftedMetricTest is
not constant in x or y. Being constant could mask errors.
@johnomotani

Copy link
Copy Markdown
Contributor Author

The previous test failure was because I'd messed up an index in shiftZ; I fixed it in c2f9eff, but was concerned that it hadn't been caught by the unit tests. It was missed because the input for the unit tests was constant in x and y, so I swapped some pairs of entries in the input field, and corresponding ones in the expected results. The updated unit test would catch the error.

@boutproject boutproject deleted a comment from codecov-io Feb 13, 2019
@bendudson

Copy link
Copy Markdown
Contributor

Is this PR good to go in now, if the conflict is fixed?

Comment thread include/bout/mesh.hxx Outdated
/// Transform a field into field-aligned coordinates
const Field3D toFieldAligned(const Field3D &f) {
return getParallelTransform().toFieldAligned(f);
const Field3D toFieldAligned(const Field3D &f, const REGION region = RGN_NOX) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should the default just be RGN_ALL since (I think) that's what it was before?

Comment thread tests/unit/mesh/parallel/test_shiftedmetric.cxx
Comment thread src/mesh/parallel/shiftedmetric.cxx Outdated
BOUT_FOR(i, mesh.getRegion2D("RGN_ALL")) {
for(int jz=0;jz<nmodes;jz++) {
BoutReal kwave=jz*2.0*PI/zlength; // wave number is 1/[rad]
fromAlignedPhs(i.x(), i.y(), jz) =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it worth caching the values of i.x(), i.y() outside the z-loop? Probably won't make much difference in the constructor, but could be more important in the other methods

(also clang-format please 🙂)

@ZedThree
ZedThree merged commit d113036 into next Feb 21, 2019
@ZedThree
ZedThree deleted the region-arguments-tofromFieldAligned branch February 21, 2019 16:17
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.

4 participants