Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f667ea6
Add multiple yup/ydown fields
ZedThree Oct 25, 2018
a972f59
Don't return *this from yup/ydown if there are no parallel slices
ZedThree Nov 7, 2018
a487a71
Merge branch 'next' into multiple-parallel-slices
ZedThree Nov 21, 2018
a66fb84
Don't reserve yup/ydown_fields vectors now that we have move ctors
ZedThree Nov 21, 2018
21d400c
Add implementation for Identity parallel transform + tests
ZedThree Nov 21, 2018
bb6fd5c
Merge branch 'next' into multiple-parallel-slices
ZedThree Jan 14, 2019
d1449bb
Implement semi-optimisation for ShiftedMetric::calcYUpDown
ZedThree Jan 14, 2019
f7fc1d8
Implement multiple parallel slices for ShiftedMetric
ZedThree Jan 14, 2019
4064d23
Guard tests that might not throw at low CHECK
ZedThree Nov 21, 2018
901b8c1
Fix test-smooth for multiple parallel slices
ZedThree Jan 14, 2019
a215003
Let interp_to in y work with multiple parallel slices
ZedThree Jan 14, 2019
1b0d149
Enable higher-order derivatives with multiple parallel slices
ZedThree Jan 14, 2019
4d065b3
Fix bug with new ShiftedMetric implementation: Arrays weren't unique
ZedThree Jan 15, 2019
bc133ec
Ensure populateStencil fills in extra guard cells from parallel slices
d7919 Jan 15, 2019
ebcf7dd
Add `ynext` to `Field2D`
d7919 Jan 15, 2019
6e6dcd6
Move macros and add YOrthogonal to list of automatic directions to re…
d7919 Jan 15, 2019
c24dc66
Switch to define and register in one step for in-build derivative met…
d7919 Jan 15, 2019
7c34454
Silence output from fft_init in ShiftedMetric tests
ZedThree Jan 15, 2019
5e6672f
Use consistent tolerance for FFT-based functions across unit tests
ZedThree Jan 15, 2019
1cbf133
Generalise Zoidberg to multiple parallel slices
ZedThree Jan 24, 2019
1e0a614
Remove unneeded yup/ydown phase caches from ShiftedMetric
ZedThree Jan 24, 2019
4978387
Generalise Zoidberg test to multiple parallel slices
ZedThree Jan 24, 2019
af2ca9b
Add proper scaling test for FCI with C2 and C4
ZedThree Jan 25, 2019
7c41f59
Generalise FCI transform to multiple parallel slices
ZedThree Jan 25, 2019
13b68c1
Throw if not enough parallel slices in FCI grid file
ZedThree Jan 25, 2019
33155db
Clean up FCI a bit
ZedThree Jan 25, 2019
e743a52
Remove old fci-slab test (replaced with better MMS test)
ZedThree Jan 25, 2019
6d95e43
Fix outdated comments
ZedThree Jan 25, 2019
49de89c
Reorder namedtuple arguments for compatibility with Python < 3.5
ZedThree Jan 29, 2019
1e3b546
Add some runtime checks for parallel slice consistency
ZedThree Jan 29, 2019
6820604
Guard unit tests for Field3D::ynext
ZedThree Jan 29, 2019
971d67e
Merge branch 'next' into multiple-yupydown
ZedThree Feb 4, 2019
a7b1bd6
Add missing assert in test_zoidberg
ZedThree Feb 4, 2019
6fb8319
Don't store fields with placeholder values in shiftedmetric
ZedThree Feb 4, 2019
3e62a35
Fix FCI MMS: g_22 should depend on the length of the field line
ZedThree Feb 4, 2019
35cdc72
Remove some unnecessary code from test-smooth
ZedThree Feb 4, 2019
69aea25
Fix: do y-interpolation in field-aligned space
ZedThree Feb 5, 2019
2074b70
Merge branch 'next' into multiple-yupydown
ZedThree Feb 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
403 changes: 176 additions & 227 deletions include/bout/index_derivs.hxx

Large diffs are not rendered by default.

33 changes: 13 additions & 20 deletions include/bout/index_derivs_interface.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -207,14 +207,13 @@ template <typename T>
T DDY(const T& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT",
REGION region = RGN_NOBNDRY) {
AUTO_TRACE();
if (std::is_base_of<Field3D, T>::value && f.hasYupYdown()
&& ((&f.yup() != &f) || (&f.ydown() != &f))) {
if (std::is_base_of<Field3D, T>::value && f.hasYupYdown()) {

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.

This hasn't changed, but this condition means that Field2D always goes through the "aligned" branch. It should actually equally be able to go through the orthogonal branch without any issue now (the previous case was that f.yup() != &f would always be false for Field2D so it would always go through the second branch anyway, so the Field3D check was just a compile-time way of ensuring this). I'm not sure which is preferred/more efficient etc. but might be worth experimenting with.

Secondly I think you proposed providing alternative names rather than yup/ydown -- is it worth using the new name in the function here (i.e. f.hasParallelSlices() rather than f.hasYupYdown()), or have these renames not happened yet?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It would be nice to be able to treat them the same!

Renames haven't happened, I was thinking of doing them in another PR in case there was any discussion on the names, but happy to just do them here (names can always be changed before the next release)

return standardDerivative<T, DIRECTION::YOrthogonal, DERIV::Standard>(f, outloc,
method, region);
} else {
const T f_aligned = f.getMesh()->toFieldAligned(f);
T result =
standardDerivative<T, DIRECTION::Y, DERIV::Standard>(f_aligned, outloc, method, region);
T result = standardDerivative<T, DIRECTION::Y, DERIV::Standard>(f_aligned, outloc,
method, region);
return f.getMesh()->fromFieldAligned(result);
}
}
Expand All @@ -223,14 +222,13 @@ template <typename T>
T D2DY2(const T& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT",
REGION region = RGN_NOBNDRY) {
AUTO_TRACE();
if (std::is_base_of<Field3D, T>::value && f.hasYupYdown()
&& ((&f.yup() != &f) || (&f.ydown() != &f))) {
if (std::is_base_of<Field3D, T>::value && f.hasYupYdown()) {
return standardDerivative<T, DIRECTION::YOrthogonal, DERIV::StandardSecond>(
f, outloc, method, region);
} else {
const T f_aligned = f.getMesh()->toFieldAligned(f);
T result = standardDerivative<T, DIRECTION::Y, DERIV::StandardSecond>(f_aligned, outloc,
method, region);
T result = standardDerivative<T, DIRECTION::Y, DERIV::StandardSecond>(
f_aligned, outloc, method, region);
return f.getMesh()->fromFieldAligned(result);
}
}
Expand All @@ -239,14 +237,13 @@ template <typename T>
T D4DY4(const T& f, CELL_LOC outloc = CELL_DEFAULT, const std::string& method = "DEFAULT",
REGION region = RGN_NOBNDRY) {
AUTO_TRACE();
if (std::is_base_of<Field3D, T>::value && f.hasYupYdown()
&& ((&f.yup() != &f) || (&f.ydown() != &f))) {
if (std::is_base_of<Field3D, T>::value && f.hasYupYdown()) {
return standardDerivative<T, DIRECTION::YOrthogonal, DERIV::StandardFourth>(
f, outloc, method, region);
} else {
const T f_aligned = f.getMesh()->toFieldAligned(f);
T result = standardDerivative<T, DIRECTION::Y, DERIV::StandardFourth>(f_aligned, outloc,
method, region);
T result = standardDerivative<T, DIRECTION::Y, DERIV::StandardFourth>(
f_aligned, outloc, method, region);
return f.getMesh()->fromFieldAligned(result);
}
}
Expand Down Expand Up @@ -312,10 +309,8 @@ template <typename T>
T VDDY(const T& vel, const T& f, CELL_LOC outloc = CELL_DEFAULT,
const std::string& method = "DEFAULT", REGION region = RGN_NOBNDRY) {
AUTO_TRACE();
bool fHasParallelSlices = (std::is_base_of<Field3D, T>::value && f.hasYupYdown()
&& ((&f.yup() != &f) || (&f.ydown() != &f)));
bool velHasParallelSlices = (std::is_base_of<Field3D, T>::value && vel.hasYupYdown()
&& ((&vel.yup() != &vel) || (&vel.ydown() != &vel)));
bool fHasParallelSlices = (std::is_base_of<Field3D, T>::value && f.hasYupYdown());
bool velHasParallelSlices = (std::is_base_of<Field3D, T>::value && vel.hasYupYdown());
if (fHasParallelSlices && velHasParallelSlices) {
return flowDerivative<T, DIRECTION::YOrthogonal, DERIV::Upwind>(vel, f, outloc,
method, region);
Expand All @@ -332,10 +327,8 @@ template <typename T>
T FDDY(const T& vel, const T& f, CELL_LOC outloc = CELL_DEFAULT,
const std::string& method = "DEFAULT", REGION region = RGN_NOBNDRY) {
AUTO_TRACE();
bool fHasParallelSlices = (std::is_base_of<Field3D, T>::value && f.hasYupYdown()
&& ((&f.yup() != &f) || (&f.ydown() != &f)));
bool velHasParallelSlices = (std::is_base_of<Field3D, T>::value && vel.hasYupYdown()
&& ((&vel.yup() != &vel) || (&vel.ydown() != &vel)));
bool fHasParallelSlices = (std::is_base_of<Field3D, T>::value && f.hasYupYdown());
bool velHasParallelSlices = (std::is_base_of<Field3D, T>::value && vel.hasYupYdown());
if (fHasParallelSlices && velHasParallelSlices) {
return flowDerivative<T, DIRECTION::YOrthogonal, DERIV::Flux>(vel, f, outloc, method,
region);
Expand Down
29 changes: 24 additions & 5 deletions include/bout/paralleltransform.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public:
* Merges the yup and ydown() fields of f, so that
* f.yup() = f.ydown() = f
*/
void calcYUpDown(Field3D &f) override {f.mergeYupYdown();}
void calcYUpDown(Field3D &f) override;

/*!
* The field is already aligned in Y, so this
Expand Down Expand Up @@ -125,7 +125,7 @@ public:
}

/// A 3D array, implemented as nested vectors
using arr3Dvec = std::vector<std::vector<std::vector<dcomplex>>>;
using arr3Dvec = std::vector<std::vector<Array<dcomplex>>>;

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.

Would #1505 help with this? What's the motivation for just changing the inner to an Array rather than the other nested levels?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, likely. I only needed the inner one to be an Array, and hadn't considered the others.

private:
Mesh &mesh; ///< The mesh this paralleltransform is part of

Expand All @@ -137,8 +137,19 @@ private:
/// Cache of phase shifts for transforming from field-aligned coordinates to X-Z orthogonal coordinates
arr3Dvec fromAlignedPhs;

arr3Dvec yupPhs; ///< Cache of phase shifts for calculating yup fields
arr3Dvec ydownPhs; ///< Cache of phase shifts for calculating ydown fields
/// Helper POD for parallel slice phase shifts
struct ParallelSlicePhase {
arr3Dvec phase_shift;
int y_offset;
};

/// Cache of phase shifts for the parallel slices. Slices are stored
/// in the following order:
Comment thread
ZedThree marked this conversation as resolved.
/// {+1, ..., +n, -1, ..., -n}
/// slice[i] stores offset i+1
/// slice[2*i + 1] stores offset -(i+1)
/// where i goes from 0 to (n-1), with n the number of y guard cells
std::vector<ParallelSlicePhase> parallel_slice_phases;

/*!
* Shift a 2D field in Z.
Expand Down Expand Up @@ -185,11 +196,19 @@ private:
* @param[in] phs Phase shift, assumed to have length (mesh.LocalNz/2 + 1) i.e. the number of modes
* @param[out] out A 1D array of length mesh.LocalNz, already allocated
*/
void shiftZ(const BoutReal *in, const std::vector<dcomplex> &phs, BoutReal *out) const;
void shiftZ(const BoutReal *in, const Array<dcomplex> &phs, BoutReal *out) const;

/// Calculate and store the phases for to/from field aligned and for
/// the parallel slices using zShift
void cachePhases();

/// Shift a 3D field \p f in Z to all the parallel slices in \p phases
///
/// @param[in] f The field to shift
/// @param[in] phases The phase and offset information for each parallel slice
/// @return The shifted parallel slices
std::vector<Field3D> shiftZ(const Field3D& f,
const std::vector<ParallelSlicePhase>& phases) const;
};


Expand Down
5 changes: 4 additions & 1 deletion include/field2d.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,10 @@ class Field2D : public Field, public FieldData {
const Field2D& ydown() const {
return *this;
}


Field2D& ynext(int UNUSED(dir)) { return *this; }
const Field2D& ynext(int UNUSED(dir)) const { return *this; }

// Operators

/*!
Expand Down
48 changes: 26 additions & 22 deletions include/field3d.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ class Mesh; // #include "bout/mesh.hxx"

#include "bout/field_visitor.hxx"

#include <vector>

/// Class for 3D X-Y-Z scalar fields
/*!
This class represents a scalar field defined over the mesh.
Expand Down Expand Up @@ -228,35 +230,37 @@ class Field3D : public Field, public FieldData {

/// Check if this field has yup and ydown fields
bool hasYupYdown() const {
return (yup_field != nullptr) && (ydown_field != nullptr);
return !yup_fields.empty() and !ydown_fields.empty();

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.

If we added a bool hasValidYUpDown, could be return hasValidYUpDown, since if mergeYupYdown() has been called, then calling yup(), etc., should be allowed so this should return true even if yup_fields is empty.

I'd been deleting yup/ydown fields on #1176, but now I've changed that to calling setHasValidYUpDown(false). 4a7cbcf is meant to have all the places that yup/ydown should be marked as invalid.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think it might just be cleaner to delete the parallel slices where we need to invalidate them. I'm not sure there's anything to be gained from keeping them

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.

👍 am I right in thinking that the std::vector will not deallocate when we call clear, so we're not actually losing anything by deleting them?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

std::vector::clear erases its contents, so the values will be lost but it does retain its capacity though, so next time we create the parallel slices we won't need to allocate any new memory, if that's what you meant.

}

/// Return reference to yup field
Field3D& yup() {
ASSERT2(yup_field != nullptr); // Check for communicate
return *yup_field;
Field3D &yup(std::vector<Field3D>::size_type index = 0) {
ASSERT2(index < yup_fields.size());
return yup_fields[index];
}
/// Return const reference to yup field
const Field3D& yup() const {
ASSERT2(yup_field != nullptr);
return *yup_field;
const Field3D &yup(std::vector<Field3D>::size_type index = 0) const {
ASSERT2(index < yup_fields.size());
return yup_fields[index];
}

/// Return reference to ydown field
Field3D& ydown() {
ASSERT2(ydown_field != nullptr);
return *ydown_field;
Field3D &ydown(std::vector<Field3D>::size_type index = 0) {
ASSERT2(index < ydown_fields.size());
return ydown_fields[index];
}

/// Return const reference to ydown field
const Field3D& ydown() const {
ASSERT2(ydown_field != nullptr);
return *ydown_field;
const Field3D &ydown(std::vector<Field3D>::size_type index = 0) const {
ASSERT2(index < ydown_fields.size());
return ydown_fields[index];
}

/// Return yup if dir=+1, and ydown if dir=-1
Field3D& ynext(int dir);
const Field3D& ynext(int dir) const;
/// Return the parallel slice at \p offset
///
/// \p offset of 0 returns the main field itself
Field3D& ynext(int offset);
const Field3D& ynext(int offset) const;

/// Set variable location for staggered grids to @param new_location
///
Expand Down Expand Up @@ -462,8 +466,8 @@ class Field3D : public Field, public FieldData {
swap(first.nz, second.nz);
swap(first.location, second.location);
swap(first.deriv, second.deriv);
swap(first.yup_field, second.yup_field);
swap(first.ydown_field, second.ydown_field);
swap(first.yup_fields, second.yup_fields);
swap(first.ydown_fields, second.ydown_fields);
swap(first.bndry_op, second.bndry_op);
swap(first.boundaryIsCopy, second.boundaryIsCopy);
swap(first.boundaryIsSet, second.boundaryIsSet);
Expand All @@ -487,8 +491,8 @@ private:
/// Time derivative (may be nullptr)
Field3D *deriv{nullptr};

/// Pointers to fields containing values along Y
Field3D *yup_field{nullptr}, *ydown_field{nullptr};
/// Fields containing values along Y
std::vector<Field3D> yup_fields{}, ydown_fields{};
};

// Non-member overloaded operators
Expand Down
79 changes: 28 additions & 51 deletions include/interpolation.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -118,61 +118,38 @@ const T interp_to(const T& var, CELL_LOC loc, REGION region = RGN_ALL) {
// At least 2 boundary cells needed for interpolation in y-direction
ASSERT0(fieldmesh->ystart >= 2);

if (var.hasYupYdown() && ((&var.yup() != &var) || (&var.ydown() != &var))) {
// Field "var" has distinct yup and ydown fields which
// will be used to calculate a derivative along
// the magnetic field
throw BoutException(
"At the moment, fields with yup/ydown cannot use interp_to.\n"
"If we implement a 3-point stencil for interpolate or double-up\n"
"/double-down fields, then we can use this case.");

if ((location == CELL_CENTRE) && (loc == CELL_YLOW)) { // C2L
BOUT_FOR(i, result.getRegion("RGN_NOBNDRY")) {
// Producing a stencil centred around a lower X value
result[i] = interp(
populateStencil<DIRECTION::YOrthogonal, STAGGER::C2L, 2>(var, i));
}
} else if (location == CELL_YLOW) { // L2C
BOUT_FOR(i, result.getRegion("RGN_NOBNDRY")) {
// Stencil centred around a cell centre
result[i] = interp(
populateStencil<DIRECTION::YOrthogonal, STAGGER::L2C, 2>(var, i));
}
// We can't interpolate in y unless we're field-aligned
// FIXME: Add check once we label fields as orthogonal/aligned

const T var_fa = fieldmesh->toFieldAligned(var);
if (region != RGN_NOBNDRY) {
// repeat the hack above for boundary points
// this avoids a duplicate toFieldAligned call if we had called
// result = toFieldAligned(result)
// to get the boundary cells
//
// result is requested in some boundary region(s)
result = var_fa; // NOTE: This is just for boundaries. FIX!
result.allocate();
result.setLocation(loc);
}

if ((location == CELL_CENTRE) && (loc == CELL_YLOW)) { // C2L
BOUT_FOR(i, result.getRegion("RGN_NOBNDRY")) {
// Producing a stencil centred around a lower X value
result[i] =
interp(populateStencil<DIRECTION::YAligned, STAGGER::C2L, 2>(var_fa, i));
}
} else {
// var has no yup/ydown fields, so we need to shift into field-aligned
// coordinates

const T var_fa = fieldmesh->toFieldAligned(var);
if (region != RGN_NOBNDRY) {
// repeat the hack above for boundary points
// this avoids a duplicate toFieldAligned call if we had called
// result = toFieldAligned(result)
// to get the boundary cells
//
// result is requested in some boundary region(s)
result = var_fa; // NOTE: This is just for boundaries. FIX!
result.allocate();
result.setLocation(loc);
} else if (location == CELL_YLOW) { // L2C
BOUT_FOR(i, result.getRegion("RGN_NOBNDRY")) {
// Stencil centred around a cell centre
result[i] =
interp(populateStencil<DIRECTION::YAligned, STAGGER::L2C, 2>(var_fa, i));
}
}

if ((location == CELL_CENTRE) && (loc == CELL_YLOW)) { // C2L
BOUT_FOR(i, result.getRegion("RGN_NOBNDRY")) {
// Producing a stencil centred around a lower X value
result[i] = interp(
populateStencil<DIRECTION::YAligned, STAGGER::C2L, 2>(var_fa, i));
}
} else if (location == CELL_YLOW) { // L2C
BOUT_FOR(i, result.getRegion("RGN_NOBNDRY")) {
// Stencil centred around a cell centre
result[i] = interp(
populateStencil<DIRECTION::YAligned, STAGGER::L2C, 2>(var_fa, i));
}
}
result = fieldmesh->fromFieldAligned(result);

result = fieldmesh->fromFieldAligned(result);
}
break;
}
case CELL_ZLOW: {
Expand Down
Loading