From 18c4ca6cc7069618a6dc26bec111d290cbaed7d6 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Fri, 25 Nov 2022 15:39:12 +0000 Subject: [PATCH 1/2] Better orthogonal separatrix spacing for nonorthogonal grids Make the transition always smooth between fixed spacing functions on the separatrix used to generate the base orthogonal grid used by a nonorthogonal grid. Removes possibility of "Weight too small. Suggest increasing poloidal 'range' settings" errors. --- doc/whats-new.md | 22 +++- hypnotoad/core/equilibrium.py | 242 ++++++++++++++++++---------------- 2 files changed, 148 insertions(+), 116 deletions(-) diff --git a/doc/whats-new.md b/doc/whats-new.md index 23675749..1981d8cb 100644 --- a/doc/whats-new.md +++ b/doc/whats-new.md @@ -1,9 +1,29 @@ What's new ========== -0.4.5 (unreleased) +0.5.0 (unreleased) ------------------ +### Breaking changes + +- Change how initial spacing of points on separatrix for nonorthogonal grids is + calculated (this spacing is used to construct the underlying orthogonal + grid). Now use weights that are just + {`cos(0.5*pi*i/index_length)**2`,`sin(0.5*pi*i/index_length)**2`} rather than + trying to construct a function that will give the same spacings when called + with the 'orthogonal spacing' that came from the initial call passed in as + `sfunc_orthogonal`. This means that the underlying orthogonal grid does not + rely at all on `nonorthogonal_*` settings, so removes an issue where some + grids could only be constructed by generating first with one set of + `nonorthogonal_*` settings, then changing them and regridding. It also + removes the possibility of a "Weight too small. Suggest increasing poloidal + 'range' settings" error, since the branch that produced that error no longer + exists. Does change the nonorthogonal grids that will be produced by + hypnotoad, although the integrated tests pass without updating the expected + results, so the changes should be small (less than 1e-9 for the integrated + test cases) (#138)\ + By [John Omotani](https://github.com/johnomotani) + ### Bug fixes - Catch error when `psi_sol_inner` is set wrong. Add suggestions to 'gradient diff --git a/hypnotoad/core/equilibrium.py b/hypnotoad/core/equilibrium.py index 683caec2..bff5924f 100644 --- a/hypnotoad/core/equilibrium.py +++ b/hypnotoad/core/equilibrium.py @@ -2884,142 +2884,154 @@ def combineSfuncs( and spacings["nonorthogonal_range_upper"] is not None ): - def new_sfunc(i): - sfixed_lower = sfunc_fixed_lower(i) + if sfunc_orthogonal is None: + # Define new_sfunc in a sensible way to create the initial distribution + # of points on the separatrix that is then used to create the orthogonal + # grid. This shouldn't rely on 'range' parameters as it should be fixed + # when nonorthogonal_* parameters are changed. Also want to avoid a + # sharp transition between sfixed_lower and sfixed_upper which might + # give odd spacings + def new_sfunc(i): + sfixed_lower = sfunc_fixed_lower(i) + sfixed_upper = sfunc_fixed_upper(i) + + # define weight_lower so it is 1. at the lower boundary and 0. at the + # upper boundary and the gradient is zero at both boundaries + weight_lower = numpy.piecewise( + i, + [i < 0.0, i > index_length], + [ + 1.0, + 0.0, + lambda i: numpy.cos(0.5 * numpy.pi * i / index_length) ** 2, + ], + ) - sfixed_upper = sfunc_fixed_upper(i) + # define weight_upper so it is 1. at the upper boundary and 0. at the + # lower boundary and the gradient is zero at both boundaries + weight_upper = numpy.piecewise( + i, + [i < 0.0, i > index_length], + [ + 0.0, + 1.0, + lambda i: numpy.sin(0.5 * numpy.pi * i / index_length) ** 2, + ], + ) - if sfunc_orthogonal is None: - sorth = None - else: + # Note weight_lower+weight_upper=1 by construction + return weight_lower * sfixed_lower + weight_upper * sfixed_upper + + else: + + def new_sfunc(i): + sfixed_lower = sfunc_fixed_lower(i) + sfixed_upper = sfunc_fixed_upper(i) sorth = sfunc_orthogonal(i) - # define weight_lower so it is 1. at the lower boundary and 0. at the - # upper boundary and the gradient is zero at both boundaries - weight_lower = numpy.piecewise( - i, - [i < 0.0, i > index_length], - [ - 1.0, - 0.0, - lambda i: numpy.exp(-((i / N_norm / this_range_lower) ** 2)), - ], - ) + # define weight_lower so it is 1. at the lower boundary and 0. at the + # upper boundary and the gradient is zero at both boundaries + weight_lower = numpy.piecewise( + i, + [i < 0.0, i > index_length], + [ + 1.0, + 0.0, + lambda i: numpy.exp( + -((i / N_norm / this_range_lower) ** 2) + ), + ], + ) - # define weight_upper so it is 1. at the upper boundary and 0. at the - # lower boundary and the gradient is zero at both boundaries - weight_upper = numpy.piecewise( - i, - [i < 0.0, i > index_length], - [ - 0.0, - 1.0, - lambda i: numpy.exp( - -(((index_length - i) / N_norm / this_range_upper) ** 2) - ), - ], - ) + # define weight_upper so it is 1. at the upper boundary and 0. at the + # lower boundary and the gradient is zero at both boundaries + weight_upper = numpy.piecewise( + i, + [i < 0.0, i > index_length], + [ + 0.0, + 1.0, + lambda i: numpy.exp( + -(((index_length - i) / N_norm / this_range_upper) ** 2) + ), + ], + ) - # make sure weight_lower + weight_upper <= 1 - weight = weight_lower + weight_upper - weight_over_slice = weight[weight > 1.0] - weight_lower[weight > 1.0] /= weight_over_slice - weight_upper[weight > 1.0] /= weight_over_slice - - if sorth is None: - # Fix spacing so that if we call combineSfuncs again for this contour - # with sfunc_orthogonal from self.contourSfunc() then we get the same - # spacing again. This is used to make the contours along the - # separatrix keep the same values when pushing the other contours - # towards orthogonal positions - # s = weight_lower*sfixed_lower - # + weight_upper*sfixed_upper - # + (1. - weight_lower - weight_upper)*s - sorth = ( - weight_lower * sfixed_lower + weight_upper * sfixed_upper - ) / (weight_lower + weight_upper) - - if numpy.any(weight_lower + weight_upper < 1e-200): - print("radial index", ix) - print(weight_lower + weight_upper) - raise ValueError( - "Weight too small. Suggest increasing poloidal 'range' " - "settings" - ) + # make sure weight_lower + weight_upper <= 1 + weight = weight_lower + weight_upper + weight_over_slice = weight[weight > 1.0] + weight_lower[weight > 1.0] /= weight_over_slice + weight_upper[weight > 1.0] /= weight_over_slice - return ( - weight_lower * sfixed_lower - + weight_upper * sfixed_upper - + (1.0 - weight_lower - weight_upper) * sorth - ) + return ( + weight_lower * sfixed_lower + + weight_upper * sfixed_upper + + (1.0 - weight_lower - weight_upper) * sorth + ) elif spacings["nonorthogonal_range_lower"] is not None: - def new_sfunc(i): - sfixed_lower = sfunc_fixed_lower(i) + if sfunc_orthogonal is None: + # Fix spacing so that if we call combineSfuncs again for this contour + # with sfunc_orthogonal from self.contourSfunc() then we get the same + # spacing again. This is used to make the contours along the separatrix + # keep the same values when pushing the other contours towards + # orthogonal positions + # s = sorth = weight_lower*sfixed_lower + (1. - weight_lower)*sorth + new_sfunc = sfunc_fixed_lower + else: - if sfunc_orthogonal is None: - sorth = None - else: + def new_sfunc(i): + sfixed_lower = sfunc_fixed_lower(i) sorth = sfunc_orthogonal(i) - # define weight_lower so it is 1. at the lower boundary and the gradient - # is zero at the lower boundary. - weight_lower = numpy.piecewise( - i, - [i < 0.0, i > index_length], - [ - 1.0, - 0.0, - lambda i: numpy.exp(-((i / N_norm / this_range_lower) ** 2)), - ], - ) - - if sorth is None: - # Fix spacing so that if we call combineSfuncs again for this contour - # with sfunc_orthogonal from self.contourSfunc() then we get the same - # spacing again. This is used to make the contours along the - # separatrix keep the same values when pushing the other contours - # towards orthogonal positions - # s = weight_lower*sfixed_lower + (1. - weight_lower)*s - sorth = sfixed_lower + # define weight_lower so it is 1. at the lower boundary and the + # gradient is zero at the lower boundary. + weight_lower = numpy.piecewise( + i, + [i < 0.0, i > index_length], + [ + 1.0, + 0.0, + lambda i: numpy.exp( + -((i / N_norm / this_range_lower) ** 2) + ), + ], + ) - return (weight_lower) * sfixed_lower + (1.0 - weight_lower) * sorth + return (weight_lower) * sfixed_lower + (1.0 - weight_lower) * sorth elif spacings["nonorthogonal_range_upper"] is not None: - def new_sfunc(i): - sfixed_upper = sfunc_fixed_upper(i) + if sfunc_orthogonal is None: + # Fix spacing so that if we call combineSfuncs again for this contour + # with sfunc_orthogonal from self.contourSfunc() then we get the same + # spacing again. This is used to make the contours along the separatrix + # keep the same values when pushing the other contours towards + # orthogonal positions + # s = sorth = weight_upper*sfixed_upper + (1. - weight_upper)*sorth + new_sfunc = sfunc_fixed_upper + else: - if sfunc_orthogonal is None: - sorth = None - else: + def new_sfunc(i): + sfixed_upper = sfunc_fixed_upper(i) sorth = sfunc_orthogonal(i) - # define weight_upper so it is 1. at the upper boundary and the gradient - # is zero at the upper boundary. - weight_upper = numpy.piecewise( - i, - [i < 0.0, i > index_length], - [ - 0.0, - 1.0, - lambda i: numpy.exp( - -(((index_length - i) / N_norm / this_range_upper) ** 2) - ), - ], - ) - - if sorth is None: - # Fix spacing so that if we call combineSfuncs again for this contour - # with sfunc_orthogonal from self.contourSfunc() then we get the same - # spacing again. This is used to make the contours along the - # separatrix keep the same values when pushing the other contours - # towards orthogonal positions - # s = weight_upper*sfixed_upper + (1. - weight_upper)*s - sorth = sfixed_upper + # define weight_upper so it is 1. at the upper boundary and the + # gradient is zero at the upper boundary. + weight_upper = numpy.piecewise( + i, + [i < 0.0, i > index_length], + [ + 0.0, + 1.0, + lambda i: numpy.exp( + -(((index_length - i) / N_norm / this_range_upper) ** 2) + ), + ], + ) - return (weight_upper) * sfixed_upper + (1.0 - weight_upper) * sorth + return (weight_upper) * sfixed_upper + (1.0 - weight_upper) * sorth else: if sfunc_orthogonal is None: From 137697753ae5848f417cb0b1943008f15a7fb472 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Fri, 25 Nov 2022 16:34:19 +0000 Subject: [PATCH 2/2] Fix .flake8 for flake8-6.0.0 No longer allows comments within an option list --- .flake8 | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.flake8 b/.flake8 index fe78d0fd..151e324b 100644 --- a/.flake8 +++ b/.flake8 @@ -1,10 +1,14 @@ [flake8] max-line-length = 89 +#E741 - 'ambiguous variable names' forbids using 'I', 'O' or 'l' +#W503 - 'line break before binary operator', but this is allowed and useful inside brackets +#E203 - 'whitespace before ':'', but black formats some slice expressions with space before ':' +#E231 - missing whitespace after ',', but black formats some expressions without space after ',' ignore = - E741, # 'ambiguous variable names' forbids using 'I', 'O' or 'l' - W503, # 'line break before binary operator', but this is allowed and useful inside brackets - E203, # 'whitespace before ':'', but black formats some slice expressions with space before ':' - E231, # missing whitespace after ',', but black formats some expressions without space after ',' + E741, + W503, + E203, + E231, exclude = hypnotoad/gui/hypnotoad_mainWindow.py, hypnotoad/gui/hypnotoad_preferences.py,