Giter Club home page Giter Club logo

hypnotoad's People

Contributors

bendudson avatar bshanahan avatar johnomotani avatar vandoo avatar zedthree avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hypnotoad's Issues

Parallelism broken by dill-0.3.5

The way parallelism is implemented requires pickling (using dill) to transfer information between processes. Some change between versions 0.3.4 and 0.3.5 of dill broke things in a couple of ways. Options objects from optionsfactory stopped being picklable - this is now fixed in the latest version (see johnomotani/optionsfactory#16), but pickling of TokamakEquilibrium objects is still failing - see uqfoundation/dill#510.

Manual updates

List of things that should be added to the manual:

  • algorithm for extending PsiContour/FineContour, used especially for nonorthogonal grids
  • algorithm for finding intersections with the wall (again for nonorthogonal)

confusing error message

The GUI gives an error message:
"'-o' is not a valid value for ls; supported values are '-', '--', '-.', ':', 'None', ' ', '', 'solid', 'dashed', 'dashdot', 'dotted'"

after the warning:

/common/projects/physics/SOLTransport/repos_eric/hyp_env/hypnotoad/hypnotoad/core/equilibrium.py:649: UserWarning: FineContour: maximum iterations (200) exceeded with ds_error 4.5852703535118056e-10
warnings.warn(

Tokamak example broken?

The example in examples/tokamak/tokamak_example.py seems to be broken, I get an error

$ ./tokamak_example.py lower-double-null.yaml 
Traceback (most recent call last):
  File "./tokamak_example.py", line 99, in <module>
    r1d, z1d, psi2d, [], [], options=options  # psi1d, fpol
TypeError: __init__() got an unexpected keyword argument 'options'

MeshRegion refactoring

The MeshRegion class includes methods that calculate geometric quantities for the standard BOUT++ locally field aligned coordinate system. Ideally MeshRegion would be generic, with the BOUT++ specific parts separated out into a derived class, e.g. BoutMeshRegion (which would then be created by BoutMesh). At the moment the only use of hypnotoad is for BOUT++, so this is not a priority at the moment.

Regression tests

It would be really nice to create an "expected output" grid file and use it for regression testing. Maybe 3 tests - single null, connected double null and disconnected double null?

Add magnetic field directions to GUI display

It would be nice if the GUI showed the direction of the poloidal (add arrow-heads to the psi contours?) and toroidal (maybe a text box with 'B_t is clockwise from above' or 'B_t is anticlockwise from above?) magnetic fields, to make them easy to check.

Adjustable region boundaries near X-points

Even when using non-orthogonal grids, the MeshRegion boundaries at X-point locations are defined by lines perpendicular to flux surfaces. This can cause problems for short-leg divertors, e.g. the region boundary could even intersect the wall (thanks @mikekryjak for finding this case!).

In principle it should be straightforward to add an option to choose the region boundary in a different way here

# set sign of step in psi towards this region from primary separatrix at start of
# region
if temp_psi_vals[-1] - start_psi > 0:
start_psi_sep_plus_delta = (
start_psi
+ self.equilibriumRegion.equilibrium.poloidal_spacing_delta_psi
)
else:
start_psi_sep_plus_delta = (
start_psi
- self.equilibriumRegion.equilibrium.poloidal_spacing_delta_psi
)
vec_points = followPerpendicular(
0,
start_point,
start_psi,
f_R=self.meshParent.equilibrium.f_R,
f_Z=self.meshParent.equilibrium.f_Z,
psivals=[start_psi, start_psi_sep_plus_delta],
rtol=self.user_options.follow_perpendicular_rtol,
atol=self.user_options.follow_perpendicular_atol,
)
self.equilibriumRegion.gradPsiSurfaceAtStart = (
vec_points[1].as_ndarray() - vec_points[0].as_ndarray()
)
# Make vector along grad(psi) at end of equilibriumRegion
end_point = self.equilibriumRegion[self.equilibriumRegion.endInd]
end_psi = self.equilibriumRegion.psi(*end_point)
# set sign of step in psi towards this region from primary separatrix at end of
# region
if temp_psi_vals[-1] - end_psi > 0:
end_psi_sep_plus_delta = (
end_psi + self.equilibriumRegion.equilibrium.poloidal_spacing_delta_psi
)
else:
end_psi_sep_plus_delta = (
end_psi - self.equilibriumRegion.equilibrium.poloidal_spacing_delta_psi
)
vec_points = followPerpendicular(
-1,
end_point,
end_psi,
f_R=self.meshParent.equilibrium.f_R,
f_Z=self.meshParent.equilibrium.f_Z,
psivals=[end_psi, end_psi_sep_plus_delta],
rtol=self.user_options.follow_perpendicular_rtol,
atol=self.user_options.follow_perpendicular_atol,
)
self.equilibriumRegion.gradPsiSurfaceAtEnd = (
vec_points[1].as_ndarray() - vec_points[0].as_ndarray()
)

(renaming the gradPsiSurfaceAtStart and gradPsiSurfaceAtEnd member variables to something more generic).

For example, the boundary could just be a straight line going away from the X-point at some specified angle (with some check that it is actually pointing into the right region and does not intersect the separatrix).

One fiddly part would be naming the options and looking up the right one for each MeshRegion, depending which divertor leg (and which radial side) the boundary belongs to.

Non-monotonic 'distance' errors with non-orthogonal grids

I tried to generate some non-orthogonal grids as a test, and got quite a few failures with an exception raised due to PsiContour.distance not being monotonic. Not sure if this was just due to the equilibrium I was using or low resolution, but it should not be happening. Distance is calculated on PsiContours by interpolating from FineContours. The way the interpolation is done means (if I remember correctly) that the distance on a PsiContour point is always in between the distance on the nearest two FineContour points, so it's always greater than the smaller of those two distances. Then if the FineContour does not reach all the way to the end of the PsiContour, the end point or two of the PsiContour can get incorrect distances, roughly as if their 'distance' is the absolute value of the distance from wherever the end of the FineContour is, so that on the first few points the 'distance' decreases (until it reaches the start of the FineContour) and then increases again. So my best guess is that the error is something to do with this, although the FineContours are supposed to extend past the end of the PsiContours so that this does not happen. There is a check/fix to make sure FineContours extend far enough here

contour.checkFineContourExtend()

Maybe it is applied too late?

I don't have time to make a minimal, reproducible example now, or to debug further, so leaving a note in case the same errors crop up in future...

Non-grid-based curvature calculation

Ideally the calculation of the curvature, Curl(b/B), should not use finite-difference derivatives on the grid, but rather derivatives of the interpolated psi-function. An implementation of this was started but not completed in MeshRegion.calc_curvature() here

elif self.user_options.curvature_type == "curl(b/B)":
# Calculate Curl(b/B) in R-Z, then project onto x-y-z components
# This calculates contravariant components of a curvature vector
raise ValueError("This option needs checking carefully before it is used")
equilib = self.meshParent.equilibrium
psi = equilib.psi
def fpol(R, Z):
return equilib.fpol(psi(R, Z))
def fpolprime(R, Z):
return equilib.fpolprime(psi(R, Z))
BR = equilib.Bp_R
BZ = equilib.Bp_Z
d2psidR2 = equilib.d2psidR2
d2psidZ2 = equilib.d2psidZ2
d2psidRdZ = equilib.d2psidRdZ
# Toroidal component of B
def Bzeta(R, Z):
return fpol(R, Z) / R
# B^2
def B2(R, Z):
return BR(R, Z) ** 2 + BZ(R, Z) ** 2 + Bzeta(R, Z) ** 2
# d(B^2)/dR
def dB2dR(R, Z):
return -2.0 / R * B2(R, Z) + 2.0 / R * (
-BZ(R, Z) * d2psidR2(R, Z)
+ BR(R, Z) * d2psidRdZ(R, Z)
- fpol(R, Z) * fpolprime(R, Z) * BZ(R, Z)
)
# d(B^2)/dZ
def dB2dZ(R, Z):
return (
2.0
/ R
* (
-BZ(R, Z) * d2psidRdZ(R, Z)
+ BR(R, Z) * d2psidZ2(R, Z)
+ fpol(R, Z) * fpolprime(R, Z) * BR(R, Z)
)
)
# dBzeta/dR
def dBzetadR(R, Z):
return -fpolprime(R, Z) * BZ(R, Z) - fpol(R, Z) / R ** 2
# dBzeta/dZ
def dBzetadZ(R, Z):
return fpolprime(R, Z) * BR(R, Z)
# dBZ/dR
def dBZdR(R, Z):
return -d2psidR2(R, Z) / R - BZ(R, Z) / R
# dBR/dZ
def dBRdZ(R, Z):
return d2psidZ2(R, Z) / R
# In cylindrical coords
# curl(A) = (1/R*d(AZ)/dzeta - d(Azeta)/dZ) * Rhat
# + 1/R*(d(R A_zeta)/dR - d(AR)/dzeta) * Zhat
# + (d(AR)/dZ - d(AZ)/dR) * zetahat
# https://en.wikipedia.org/wiki/Del_in_cylindrical_and_spherical_coordinates,
#
# curl(b/B) = curl((BR/B2), (BZ/B2), (Bzeta/B2))
# curl(b/B)_R = 1/(R*B2)*d(BZ)/dzeta - BZ/(R*B4)*d(B2)/dzeta
# - 1/B2*d(Bzeta)/dZ + Bzeta/B4*d(B2)/dZ
# = -1/B2*d(Bzeta)/dZ + Bzeta/B4*d(B2)/dZ
# curl(b/B)_Z = Bzeta/(R*B2) + 1/B2*d(Bzeta)/dR - Bzeta/B4*d(B2)/dR
# - 1/(R*B2)*d(BR)/dzeta + BR/(R*B4)*d(B2)/dzeta
# = Bzeta/(R*B2) + 1/B2*d(Bzeta)/dR - Bzeta/B4*d(B2)/dR
# curl(b/B)_zeta = 1/B2*d(BR)/dZ - BR/B4*d(B2)/dZ
# - 1/B2*d(BZ)/dR + BZ/B4*d(B2)/dR
# remembering d/dzeta=0 for axisymmetric equilibrium
def curl_bOverB_R(R, Z):
return -dBzetadZ(R, Z) / B2(R, Z) + Bzeta(R, Z) / B2(R, Z) ** 2 * dB2dZ(
R, Z
)
def curl_bOverB_zeta(R, Z):
return (
dBRdZ(R, Z) / B2(R, Z)
- BR(R, Z) / B2(R, Z) ** 2 * dB2dZ(R, Z)
- dBZdR(R, Z) / B2(R, Z)
+ BZ(R, Z) / B2(R, Z) ** 2 * dB2dR(R, Z)
)
def curl_bOverB_Z(R, Z):
return (
Bzeta(R, Z) / (R * B2(R, Z))
+ dBzetadR(R, Z) / B2(R, Z)
- Bzeta(R, Z) / B2(R, Z) ** 2 * dB2dR(R, Z)
)
# A^x = A.Grad(x)
# A^y = A.Grad(y)
# A^z = A.Grad(z)
# dpsi/dR = -R*Bp_Z
# dpsi/dZ = R*Bp_R
def curl_bOverBx(R, Z):
return curl_bOverB_R(R, Z) * (-R * BZ(R, Z)) + curl_bOverB_Z(R, Z) * (
R * BR(R, Z)
)
self.curl_bOverBx = curl_bOverBx(self.Rxy, self.Zxy)
# Grad(y) = (d_Z, 0, -d_R)/(hy*cosBeta)
# = (BR*cosBeta-BZ*sinBeta, 0, BZ*cosBeta+BR*sinBeta)/(Bp*hy*cosBeta)
# = (BR-BZ*tanBeta, 0, BZ+BR*tanBeta)/(Bp*hy)
curl_bOverBy = (
curl_bOverB_R(self.Rxy, self.Zxy)
* (BR(self.Rxy, self.Zxy) - BZ(self.Rxy, self.Zxy) * self.tanBeta)
+ curl_bOverB_Z(self.Rxy, self.Zxy)
* (BZ(self.Rxy, self.Zxy) + BR(self.Rxy, self.Zxy) * self.tanBeta)
) / (self.Bpxy * self.hy)
self.curl_bOverBy = curl_bOverBy
# Grad(z) = Grad(zeta) - Bt*hy/(Bp*R)*Grad(y) - I*Grad(x)
self.curl_bOverBz = (
curl_bOverB_zeta(self.Rxy, self.Zxy) / self.Rxy
- self.Btxy * self.hy / (self.Bpxy * self.Rxy) * self.curl_bOveryBy
- self.I * self.curl_bOverBx
)
# bxcv is calculated this way for backward compatibility with Hypnotoad.
# bxcv stands for 'b x kappa' where kappa is the field-line curvature, which
# is not exactly equivalent to the result here, but this is how Hypnotoad
# passed 'curvature' calculated as curl(b/B)
self.bxcvx = self.Bxy / 2.0 * self.curl_bOverBx
self.bxcvy = self.Bxy / 2.0 * self.curl_bOverBy
self.bxcvz = self.Bxy / 2.0 * self.curl_bOverBz

Save wall points?

At least when creating a grid from a geqdsk file, the data is saved already because we save the geqdsk file, but it might be nice to save the wall points in a more accessible form in the grid file - can be useful for analysis and post-processing.

String outputs should use attributes, not variables

String outputs, like parallel_transform, hypnotoad_inputs and hypnotoad_git_hash should be written as global attributes so that BOUT++ can read them with Mesh::get(). That is required for consistency with IDL hypnotoad's grid files too.

This needs 'global' (or 'file-level') attribute support in boututils.datafile.DataFile (to avoid horrible hacks at least).

Definition of dy in the core

Following discussion with H.Seto, it would be useful for post-processing if dy was defined so that y went from 0 to 2pi in the core region of tokamak grids. Is there a way to do this, while allowing for cases like Torpex where there isn't a core region?

Module packaging

Would be nice if hypnotoad2 was structured better and pip-installable. Todo:

  • Move all the *.py files under a hypnotoad2 subdirectory (except the ones that should be executable?)
  • Create a setup.py (including an entry_point for anything that should be executable)
  • Upload on PyPi

@ZedThree @bendudson please feel free to expand the list above with anything I've missed!

Refactor spacing functions

The implementation of the spacing functions (EquilibriumRegion.combineSfuncs(), etc.) has evolved into something horribly tangled. I think one of the uses has combineSfuncs() call fixedSpacingFunc() call combineSfuncs() with different arguments or something horrible like that. The whole lot could do with refactoring.

Better examples (support for GUI)

It would be nice if examples/tokamak generated a geqdsk file from it's analytical psi-function, which could then be used with hypnotoad-gui or hypnotoad_geqdsk to create a grid file. Working with a geqsk file would demonstrate the typical workflow (the example was written before those interfaces existed!).

Label psinorm in GUI plots

It would be nice to have the option (a check box maybe?) to plot the psi contours in the GUI with normalised psi rather than the dimensional psi. This would make it easier to pick psinorm_* values.

User interface

It would be nice to have an interface (maybe even graphical?) that makes it easier to prototype/experiment when creating an input file than just passing input file names to a command line script like hypnotoad2_geqdsk experimental_equilib.g my_grid.yml.

There are already plotting functions which could be used to provide output.

Possible features:

  • The aim of the interface would be to generate a final input file, so that whatever grid is generated can be recreated directly by running from the command line.
  • For prototyping, it would be nice to have a second set of options (for example reduced nx and finecontour_Nfine, and possibly some looser atols) which would make iterating on the original grid quicker.
  • Import for example TokamakEquilibrium.default_options and provide some sort of browser where the user could see the current and default settings for all options, see help/description, and modify the values. Done in #17
  • Create and show a prototype grid, using the current prototype options (not sure if it's useful to have an option to generate check the Mesh.geometry() output for the prototype?).
  • Save a .yml file with the final settings, and generate a grid-file from it. Done in #17.
    • possibly browse the created grid, metric coefficients, etc?
    • integrate with xBOUT to help plotting the final results?
  • Load an existing .yml input file, to prototype changes to it interactively. Done in #17.

We could probably achieve quite a bit of this quickly by providing methods/functions that could be called at the Python interactive prompt. Maybe a template Jupyter notebook containing a suggested grid-generation workflow. A hypnotoad1-like gui interface would be the nicest option... GUI added in #17 thanks to @ZedThree! Some extras for the gui that would be nice to have:

  • hide options that aren't used (e.g. a bunch of nonorthogonal settings if orthogonal = True.
  • still plot psi if finding X-points and O-points fails - I tried this for one geqdsk file (not sure what was in it, might have been TORPEX!) and hypnotoad-gui exited; would be nice to have an error message and a plot of psi to see what went wrong
  • plot the wall contour (if it exists). Done in #23.
  • Always save an input file along with the final generated grid file (although all the hypnotoad2 options should be saved to the grid file already).
  • Add undo stack
  • Keep equilibrium/mesh plots in separate tabs

Testing

There are some unit tests for the core PsiContour, EquilibriumRegion, MeshRegion, Mesh components, but I (@johnomotani) don't think it's as complete as I would like.

Also would be nice to test/check the expressions implemented for metric coefficients, etc. Is it possible to provide a set of analytic inputs for which the metric components, curvature, etc. can be calculated exactly and check against them? Most of the expressions should only depend on the finecontour_Nfine and finecontour_atol parameters, which can influence the approximation to the y-coordinate, but should be otherwise independent of grid resolution. A few expressions (e.g. the curvature) do depend on grid resolution, and we could check the convergence.

Parallelisation and optimisation

In principle hypnotoad2 is embarassingly parallelisable. The most expensive part tends to be refining the FineContours (at least when finecontour_Nfine is large), and every FineContour is independent. However, functions are not pickle-able which I think makes using multiprocessing tricky. There might be workarounds for this though https://medium.com/@emlynoregan/serialising-all-the-functions-in-python-cd880a63b591.

Other parts of the code could be parallelised, though it might not be worth it as probably they do not typically take long anyway. For example most if not all of the output fields (metric coefficients, etc.) are independent and could be calculated simultaneously. dask might be able to do this with minimal changes (possibly convert arrays to dask arrays, and then do

g11, g22, g33, ... = dask.compute(g11, g22, ...)

just before writing them out.

Options class replacement

As discussed here #25 (comment) there are various issues with using the options module for handling settings in hypnotoad:

@dschwoerer

  • I just wanted to mention that options pulls in additional dependencies. As options doesn't even seem to have a public bug tracker (same is true for some dependencies) I do worry about pulling that in ...
  • options allows getting options simply as e.g. self.options.orthogonal, but "that is only a few lines [to implement], I [@dschwoerer] did that to drop the dependency on Bunch."

@ZedThree

  • not being able to deepcopy
  • removing keys from the passed-in object
  • To make the options form in the gui more useful, I think we would want to add some additional features to the options class at any rate:
    • keeping track of documentation
    • expected type of values, allowed values. For example, make refine_method into a drop-down list of the available methods.

A replacement Options class would be welcome. Maybe a possibility for something shared with xBOUT boutproject/xBOUT#94 boutproject/xBOUT#97?

Documentation

Need some documentation.

At least:

  • how-to run hypnotoad2 to create tokmak (or TORPEX X-point) grids
  • description of what all the options that can be set in .yaml input files do

Would be nice to have:

  • notes on common problems when gridding (e.g. grid goes over the edge of the input psi-grid, etc.) and hints on how to resolve
  • use ~~click to get command line options, so we can~~~~sphinx-argparse to document the options and provide help and usage instructions.
  • a derivation and explanation of non-orthogonal coordinate system and implementation.

Move to `pyproject.toml`?

Having all of the build config in pyproject.toml is now well supported, and now even editable installs work without setup.py since pip 22. PEP 621 defines some standard keys, so people can use new and weird build tools like poetry or flit if they like.

I'm happy to implement this if it would be useful!

zShift accuracy

At the moment zShift is calculated in MeshRegion.calcZShift() by integrating on the PsiContours (integrating in y). It would be nicer to integrate on FineContours (as is done for the poloidal distance) and then interpolate to the final grid positions, because then zShift would be more accurate (as long as Nfine is large), and also zShift and shiftAngle would be guaranteed to be consistent when the y-resolution is changed.

Better nonorthogonal poloidal spacing algorithm

The current algorithm for defining poloidal spacing for nonorthogonal grids is not particularly robust, and requires a significant amount of manual tweaking to produce a good grid. An improved method would be very nice!

The current algorithm defines a 'spacing function' for each PsiContour independently. The parameters of the spacing functions vary in a smooth way radially in order to create a sensible grid. This method was chosen in large part because it was convenient to implement.

Now that there are the FineContour objects (which are indepedent of the PsiContour poloidal spacing) providing the underlying skeleton for the grid, and there is the regrid() functionality, it would be possible to implement other algorithms. PsiContour.getRegridded() could easily be modified to accept an array of poloidal positions instead of a spacing function, by modifying this block

if sfunc is not None:
s = sfunc(indices)
# offset fine_contour.interpFunction in case sfunc(0.)!=0.
sbegin = sfunc(0.0)
else:
d = self.get_distance(psi=psi)
s = (d[self.endInd] - d[self.startInd]) / (npoints - 1) * indices
sbegin = 0.0
so s is taken from the array passed in instead of calculated from sfunc. Then it would be possible to do some sort of iteration (pseudo-timestepping?) on the array of poloidal positions.

For example, given an array of poloidal positions:

  1. call a suitably modified MeshRegion.distributePointsNonorthogonal() to get the R-Z positions
  2. work out how 'nonorthogonal' the grid is at each grid point, and which direction it would have to move to make the grid more orthogonal - turn this into an effective 'force' on the grid point
  3. define another 'force' that repels each point from other points on the same contour
  4. combine the forces to work out an update for the position of each point

...and iterate until a relaxed state is found. To control the grid spacing and deal with non-orthogonal features of the grid (like target plates) it would probably be necessary to have some parameters that adjust the 'forces' depending on position in the grid:

  • enhanced or decreased 'spring constant' for the repulsion between points near the targets or X-point, set by nonorthogonal_*_poloidal_spacing_length.
  • different strengths for the force that pushes the grid towards orthogonal - this should probably be stronger than the repulsion between grid points when far from X-points or targets (so that a set of grid points is pushed onto a line that follows Grad(psi) perpendicular to flux surfaces, and then that line is moved around as a rigid object relative to the nearby lines), but weaker than the repulsion between grid points when near the targets or X-point, so that the spacing does become non-orthogonal there. Controlled by (some of) the nonorthogonal_*_poloidal_spacing_range* parameters?

If implementing this suggestion, it would probably be a good idea to parallelise MeshRegion.distributePointsNonorthogonal(). The reason this method was not parallelised before was to avoid having to construct sfunc_orthogonal_list on the parallel workers (have to construct on the workers because functions cannot be serialised by dill to be communicated), but the 'orthogonal spacing function' would not be required in the new method.

As another possible speed-up, it should probably be OK to skip the PsiContour.refine() calls until the iteration is nearly converged, as the interpolation from FineContour objects should be pretty accurate.

As an extension of the above suggestion, with a more substantial refactoring of the code, it would be possible to allow the PsiContour objects (for nonorthogonal grids) to extend between (what are currently) different regions - i.e. a single PsiContour object would extend from target to target in the SOL or PFRs, or all the way around the core. Then even the poloidal boundaries between regions could be allowed to move, which might be useful for some configurations (e.g. where the divertor legs are very short so the X-point is close to the wall).

Possibly the region boundaries could be updated in a different way, with the structure kept more like it is at present but with the boundaries being moved. The advantage of the suggestion above (compared to this one) is that the FineContour objects would extend from target to target, etc. and so would not need to be changed when the region boundaries move.

Read plasma profiles from geqdsk files?

The IDL hypnotoad created density and temperature profiles from the equilibrium. Should we add a feature to create them? Do eqdsk files always provide pressure profiles?

Save geqdsk into grid file?

Would it be a good idea when creating a grid file from a geqdsk equilibrium file to save the entire geqdsk file into the grid file, e.g. as a massive string? Pro: ensures the geqdsk file can be recovered. Con: grid file is a bit bigger.

Name?

Stupid question, but wouldn't hypnotoad be a better name?

As far as I understand, this is supposed to do basically the same thing as the old hypnotoad (more or less). So a user wouldn't really care whether this is an improved version or a rewrite, it is basically version 2 of hypnotoad.

Thinking from the user perspective, I want hypnotoad, thus I install hypnotoad. Whether that is version 1 or 2 (or 3) doesn't matter that much. Thus I can just pip install hypnotoad and get the best (newest) version.

https://pypi.org/project/hypnotoad/ is still available ๐Ÿ‘

Analytic equilibria

Parameterised, analytic equilibria can be created following the recipe in [Cerfon&Freidberg (2010)]. There is already a Python implementation https://github.com/johnomotani/CerfonFreidbergGeometry (although it could do with a few cosmetic upgrades). It would be nice to add a new tab to the hypnotoad GUI which could allow a user to tweak the parameters of the analytic equilibrium and save them into an input .yml file. Then the Psi function from CerfonFreidberg could be passed to TokamakEquilibrium (which should only need a small upgrade to TokamakEquilibrium), allowing hypnotoad to grid the analytical equilibrium.

Edit (updating comment below): Should note the health warning with these analytic equilibria - the equilibrium implies a pressure profile, which will generally not be consistent with a real machine or with the plasma solution produced by your code. Outside the separatrix things are probably even more dodgy - there's an analytic form for psi, but it assumes that the pressure is a linear function of psi, which is not realistic as in the SOL pressure is (a) not a flux function and (b) should drop quickly to ~0 and cannot (physically) be negative...

Negative Jacobian

Can we modify the coordinate system slightly, so that the Jacobian is always positive?

BOUT++ currently rejects inputs with negative Jacobians, but this is what happens when the poloidal field is negative.

In https://bout-dev.readthedocs.io/en/latest/user_docs/coordinates.html can we just remove the factor of sigma (sign of Bpol) from the definition of Z? This would reverse the sign of g^xz and g^yz, change some elements of g.. and mean that J was always positive. It would also mean that:

  • x is always outwards from core to SOL
  • y is always clockwise in the right hand cut-through of the tokamak
  • z is always toroidal angle, anti-clockwise looking from the top

Testing refine methods

There are now several methods for refining points: line, newton, integrate, integrate+newton.
Only line is tested in test_equilbrium.py, and using other methods sometimes fails where line passes. I suspect this is because the tests start with points very far from the contour, and were set up in such a way that line would find the right contour, so this does not indicate a problem with the other methods. The failure of integrate may just be a question of slightly too tight tolerance (only fails test_refine), and integrate+newton fails to converge, but only on the same test. newton fails to converge on several tests.

No complete grid file created with the tokamak example

Hi. I have been trying to create a grid using the tokamak example provided. I can get the visual representation of the grid but the file created seems to be incomplete as I get the following error

DataFile: None passed as data to write. Ignoring
DataFile: None passed as data to write. Ignoring

I have tried to use the incomplete file in a simulation, and that also comes up with errors

Is there any way of using the tokamak example script to create a usable grid file?

Better algorithm for extending `PsiContour`

In the temporaryExtend() method used to extend a PsiContour

def temporaryExtend(

at the moment the initial guess for new points is made by extrapolating from the positions of nearby points. It would be more accurate and probably more robust to use an ODE integrator to follow the flux surface, similar to how the separatrix is traced here
# Integrate a distance "step" along the leg
solve_result = solve_ivp(
dpos_dl,
(0.0, step),
pos,
rtol=0.0,
atol=self.user_options.leg_trace_atol,
)
newpos = (solve_result.y[0][1], solve_result.y[1][1])

Changing string options crashes GUI

Trying to change the value of a string-type option in the GUI causes a crash. Can work around by setting in an input file and loading that.

dy is not calculated correctly

total(dy) = 10.77, not 6.28.

   # constant spacing in y for now
    if self.ny_core > 0:
        # If there is a core region, set dy consistent with 0<=y<2pi in the core
       self.dy_scalar = 2.0 * numpy.pi / self.ny_core
    else:
        # No core region, set dy consistent with 0<=y<2pi in whole domain
        self.dy_scalar = 2.0 * numpy.pi / self.ny_noguards

For my test ny_... = 8/24/8/12/32/16, dy should be: 2np.pi/96=0.065.
But grid['dy']=0.1122. It seems only to count the outer half, 2
np.pi/(16*2+32)=0.1122.

Tokamak grids should check safety factor

GEQDSK equilibrium files come in different flavours, with different definitions of poloidal flux (see PR #172). The files should also include the safety factor, that can be checked once the grid has been generated.

Hang when program finishes if running parallelised

When parallel execution is enabled, hypnotoad often (always?) hangs when the program finishes, and needs to be terminated with ^C.

It seems there must be somehow a circular reference somewhere, maybe between Mesh and its ParallelMap member variable?

Edit: Actually the __del__() methods do (at least sometimes) get called after a keyboard interrupt. Maybe that suggests the hang is somewhere else? Unless the interrupt somehow breaks some circular reference chain and lets the garbage collection gets started??

Note: this 'hang' happens after grid generation is finished and the grid file is closed, so it should not cause any errors.

List index out of range in wallInteraction

hypnotoad2: hypnotoad2_geqdsk mast.geqdsk 
  nx = 65, ny = 65
102

Options
=======
Name                                              |  Value                         
--------------------------------------------------------------------------------
cap_Bp_ylow_xpoint                                |  False      (default)               
curvature_type                                    |  curl(b/B) with x-y derivatives     (default)
finecontour_Nfine                                 |  100        (default)                 
finecontour_atol                                  |  1e-12      (default)               
finecontour_diagnose                              |  False      (default)               
finecontour_maxits                                |  200        (default)                 
follow_perpendicular_atol                         |  1e-08      (default)               
follow_perpendicular_rtol                         |  2e-08      (default)               
geometry_rtol                                     |  1e-10      (default)               
nonorthogonal_radial_range_power                  |  1.0        (default)                 
nonorthogonal_spacing_method                      |  combined   (default)            
nonorthogonal_target_poloidal_spacing_length      |  None       (default)                
nonorthogonal_target_poloidal_spacing_range       |  None       (default)                
nonorthogonal_target_poloidal_spacing_range_inner |  None       (default)                
nonorthogonal_target_poloidal_spacing_range_outer |  None       (default)                
nonorthogonal_xpoint_poloidal_spacing_length      |  0.05       (default)                
nonorthogonal_xpoint_poloidal_spacing_range       |  None       (default)                
nonorthogonal_xpoint_poloidal_spacing_range_inner |  None       (default)                
nonorthogonal_xpoint_poloidal_spacing_range_outer |  None       (default)                
nx_core                                           |  5  (default)                   
nx_pf                                             |  5                             
nx_pf_lower                                       |  None       (default)                
nx_pf_upper                                       |  None       (default)                
nx_sol                                            |  5  (default)                   
nx_sol_inner                                      |  5                             
nx_sol_outer                                      |  5                             
ny_inner_divertor                                 |  4  (default)                   
ny_inner_lower_divertor                           |  4                             
ny_inner_sol                                      |  4                             
ny_inner_upper_divertor                           |  4                             
ny_outer_divertor                                 |  4  (default)                   
ny_outer_lower_divertor                           |  4                             
ny_outer_sol                                      |  4                             
ny_outer_upper_divertor                           |  4                             
ny_sol                                            |  8  (default)                   
orthogonal                                        |  True       (default)                
poloidal_spacing_delta_psi                        |  0.00125012279304738           
poloidal_spacing_method                           |  sqrt       (default)                
poloidalfunction_diagnose                         |  False      (default)               
psi_core                                          |  -0.11251105137437345          
psi_pf_lower                                      |  -0.11251105137437345          
psi_pf_upper                                      |  -0.11251105137437345          
psi_sol                                           |  -0.13751350723532105          
psi_sol_inner                                     |  -0.13751350723532105          
psi_spacing_separatrix_multiplier                 |  None       (default)                
psinorm_core                                      |  0.9        (default)                 
psinorm_pf                                        |  0.9                           
psinorm_pf_lower                                  |  0.9                           
psinorm_pf_upper                                  |  0.9                           
psinorm_sol                                       |  1.1        (default)                 
psinorm_sol_inner                                 |  1.1                           
refine_atol                                       |  2e-08      (default)               
refine_methods                                    |  integrate+newton, integrate        (default)
refine_width                                      |  0.01       (default)                
sfunc_checktol                                    |  1e-13      (default)               
shiftedmetric                                     |  True       (default)                
target_poloidal_spacing_length                    |  None       (default)                
xpoint_poloidal_spacing_length                    |  0.05       (default)                
y_boundary_guards                                 |  0  (default)                   

Traceback (most recent call last):
  File "/home/peter/Codes/hypnotoad2/hypnotoad_test_venv/bin/hypnotoad2_geqdsk", line 11, in <module>
    load_entry_point('hypnotoad2', 'console_scripts', 'hypnotoad2_geqdsk')()
  File "/home/peter/Codes/hypnotoad2/hypnotoad2/scripts/hypnotoad2_geqdsk.py", line 32, in main
    eq = tokamak.read_geqdsk(fh, options=options)
  File "/home/peter/Codes/hypnotoad2/hypnotoad2/cases/tokamak.py", line 1521, in read_geqdsk
    **kwargs
  File "/home/peter/Codes/hypnotoad2/hypnotoad2/cases/tokamak.py", line 236, in __init__
    self.makeRegions()
  File "/home/peter/Codes/hypnotoad2/hypnotoad2/cases/tokamak.py", line 489, in makeRegions
    leg_regions, core_regions, segments, connections = self.describeDoubleNull()
  File "/home/peter/Codes/hypnotoad2/hypnotoad2/cases/tokamak.py", line 722, in describeDoubleNull
    lower_legs = self.findLegs(lower_x_point)
  File "/home/peter/Codes/hypnotoad2/hypnotoad2/cases/tokamak.py", line 323, in findLegs
    intersect = self.wallIntersection(Point2D(*pos), Point2D(*newpos))
  File "/home/peter/Codes/hypnotoad2/hypnotoad2/core/equilibrium.py", line 2967, in wallIntersection
    closed_wall = self.wall + [self.wall[0]]
IndexError: list index out of range

This is with a GEQDSK file generated by freegs from the 03-mast.py example (attached, ignore the .log extension that's just so github will let me upload it!)
mast.geqdsk.log

Support core-only, SOL-only or PFR-only grids

At the moment the supported geometries for tokamak grids are single-null and double-null and must include one or two X-points. It would be nice to have more options:

  • grid only in the core - should be fairly strightforward. If psinorm_sol < 1 then the separatrix is outside the requested region. Instead of using the separatrix as the base contour use some closed contour (not sure whether the psi_inner, psi_outer or 0.5*(psi_inner + psi_outer) would be best)
  • grid only in the SOL - fairly simple again for a single-null; double-null probably needs another option to specify whether to grid the outer SOL or inner SOL
  • grid only in the PFR - again need some option to be able to choose to grid lower or upper PFR instead of core

Backward compatibility with 'hthe'

The poloidal spacing variable hy corresponds to hthe in the original Hypnotoad, but is defined slightly differently for non-orthogonal grids. For that reason, I named the variable differently, and the variable in the netCDF output files is named hy.

The downside of the current implementation is that existing codes may calculate metric coefficients using hthe as input. This can be useful as normalisation can be simpler on basic R and hy (lengths) and B, Bt, Bp (magnetic fields) than on metric coefficients (when psi is used as radial coordinate lengths and magnetic field units get mixed up in the coordinate system).

The 'problem' with changed definition of hy vs. hthe only affects non-orthogonal grids. In practice, I guess there are no production codes depending on the Hypnotoad-1 style non-orthogonal grids, so it is probably not an issue to save hthe to the grid files, because in practice no one is depending on the old definition. On the other hand, saving as hthe provides better backward-compatibility with codes using orthogonal grids.

So I'd propose to update hypnotoad to save the variable as hthe. Thoughts @bendudson @ZedThree @bshanahan?

Fix options-to-string method

The options are converted to a string here

def _getOptionsAsString(self):
import yaml
result = ""
result += yaml.dump(self.user_options)
mesh_options_dict = {"Mesh": {}}
m = mesh_options_dict["Mesh"]
for key, val in self.user_options.items():
if val is not None:
m[key] = str(val)
result += yaml.dump(mesh_options_dict)
return result

It seems like this method should have been changed when the options were refactored - there is now only self.user_options, not separate self.equilibOptions and self.user_options. The parts on l.3515-3519 can probably be deleted now.

Ideally, this method should be reversible to produce a dict equivalent to user_options. We could test that by checking with a test Mesh something like:

assert dict_equiv(dict(mesh.user_options), yaml.load(mesh._getOptionsAsString())

I think this isn't a serious bug, as the only effect should be that the options all get printed out twice at the moment - the information should all be there though.

preferences not working

change the plot options in the preference tab would make the GUI crash, the error message:

Traceback (most recent call last):
  File "/projects/physics/SOLTransport/repos_eric/hyp_env/lib/python3.9/site-packages/hypnotoad/gui/gui.py", line 578, in accept
    self.parent.gui_options.set(
AttributeError: 'dict' object has no attribute 'set'
Aborted

Delete the wiki?

The troubleshooting information from the wiki (https://github.com/boutproject/hypnotoad/wiki) has now been included in the manual (sections https://hypnotoad.readthedocs.io/en/stable/tips-and-tricks.html and https://hypnotoad.readthedocs.io/en/stable/nonorthogonal-tips.html). It's probably not good to have multiple places for information - it's harder to search and harder to keep up to date. Options:

  1. Delete the wiki (it would be good if someone could check that anything in the wiki that might be useful has made it into the manual). Additions and further tips should just be added to the manual (and can be discussed/refined while reviewing a PR).
  2. Remove the information that is included in the manual from the wiki, but keep the wiki as a more informal place for collecting tips and tricks to be written up in the manual at some point in the future if they are useful.

Any preferences or other options? I'd probably vote for 1 because having more than one place to look for stuff is bad.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.