Skip to content

Commit 199fd1e

Browse files
authored
Merge branch 'master' into poloidal_distance
2 parents 936b0b2 + e4a1dff commit 199fd1e

File tree

8 files changed

+138
-815
lines changed

8 files changed

+138
-815
lines changed

doc/whats-new.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Release history
3333
By [John Omotani](https://github.com/johnomotani)
3434
- Grids that are nonorthogonal only at the X-point (#180).
3535
By [John Omotani](https://github.com/johnomotani)
36-
- Script to convert UEDGE grid file to BOUT++ grid file (#136).
36+
- Save Jpar0 to output tokamak grids (#177).
3737
By [Ben Dudson](https://github.com/bendudson)
3838
- Add flag for `tokamak_example.py` to generate grids with reversed current. This
3939
prevents negative `dx`/`J` and allows them to be run in BOUT++.

hypnotoad/cases/tokamak.py

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,8 @@ def __init__(
347347
psi1D,
348348
fpol1D,
349349
pressure=None,
350+
fprime=None,
351+
pprime=None,
350352
wall=None,
351353
psi_axis_gfile=None,
352354
psi_bdry_gfile=None,
@@ -370,6 +372,8 @@ def __init__(
370372
--------
371373
372374
pressure[nf] = 1D array of pressure as a function of psi1D [Pa]
375+
fprime[nf] = 1D array of df / dpsi
376+
pprime[nf] = 1D array of dp / dpsi . If set then pressure must also be set.
373377
374378
wall = [(R0,Z0), (R1, Z1), ...]
375379
A list of coordinate pairs, defining the vessel wall.
@@ -392,6 +396,9 @@ def __init__(
392396
393397
"""
394398

399+
if pprime is not None:
400+
assert pressure is not None
401+
395402
self.user_options = self.user_options_factory.create(settings)
396403

397404
if self.user_options.reverse_current:
@@ -439,12 +446,22 @@ def __init__(
439446
# fpol constant in SOL
440447
fpol1D = np.concatenate([fpol1D, np.full(psiSOL.shape, fpol1D[-1])])
441448

449+
if fprime is not None:
450+
fprime = np.concatenate([fprime, np.full(psiSOL.shape, 0.0)])
451+
442452
if pressure is not None:
443453
# Use an exponential decay for the pressure, based on
444454
# the value and gradient at the plasma edge
445455
p0 = pressure[-1]
446456
# p = p0 * exp( (psi - psi0) * dpdpsi / p0)
447-
pressure = np.concatenate([pressure, p0 * np.exp(psiSOL * dpdpsi / p0)])
457+
pressure = np.concatenate(
458+
[pressure, p0 * np.exp((psiSOL - psi1D[-1]) * dpdpsi / p0)]
459+
)
460+
461+
if pprime is not None:
462+
pprime = np.concatenate(
463+
[pprime, dpdpsi * np.exp((psiSOL - psi1D[-1]) * dpdpsi / p0)]
464+
)
448465

449466
self.magneticFunctionsFromGrid(
450467
R1D, Z1D, psi2D, self.user_options.psi_interpolation_method
@@ -464,19 +481,32 @@ def __init__(
464481
# ext=3 specifies that boundary values are used outside range
465482

466483
# Spline representing the derivative of f
467-
self.fprime_spl = self.f_spl.derivative()
484+
if fprime is not None:
485+
self.fprime_spl = interpolate.InterpolatedUnivariateSpline(
486+
psi1D * self.f_psi_sign, fprime, ext=3
487+
)
488+
else:
489+
self.fprime_spl = self.f_spl.derivative()
468490
else:
469-
self.f_spl = lambda psi: 0.0
470-
self.fprime_spl = lambda psi: 0.0
491+
self.f_spl = lambda psi: 0.0 * psi
492+
self.fprime_spl = lambda psi: 0.0 * psi
471493

472494
# Optional pressure profile
473495
if pressure is not None:
474496
self.p_spl = interpolate.InterpolatedUnivariateSpline(
475497
psi1D * self.f_psi_sign, pressure, ext=3
476498
)
499+
500+
if pprime is not None:
501+
self.pprime_spl = interpolate.InterpolatedUnivariateSpline(
502+
psi1D * self.f_psi_sign, pprime, ext=3
503+
)
504+
else:
505+
self.pprime_spl = self.p_spl.derivative()
477506
else:
478507
# If no pressure, then not output to grid file
479508
self.p_spl = None
509+
self.pprime_spl = None
480510

481511
# Find critical points (O- and X-points)
482512
R2D, Z2D = np.meshgrid(R1D, Z1D, indexing="ij")
@@ -506,6 +536,8 @@ def __init__(
506536

507537
if len(xpoints) == 0:
508538
warnings.warn("No X-points found in TokamakEquilibrium input")
539+
self.psi_bdry = psi_bdry_gfile
540+
self.psi_bdry_gfile = psi_bdry_gfile
509541
else:
510542
self.psi_bdry = xpoints[0][2] # Psi on primary X-point
511543
self.x_point = Point2D(xpoints[0][0], xpoints[0][1])
@@ -1685,9 +1717,14 @@ def createRegionObjects(self, all_regions, segments):
16851717
eqreg.pressure = lambda psi: self.pressure(
16861718
leg_psi + sign * abs(psi - leg_psi)
16871719
)
1720+
# Set pprime and fprime to zero so Jpar0 = 0
1721+
eqreg.pprime = lambda psi: 0.0
1722+
eqreg.fprime = lambda psi: 0.0
16881723
else:
1689-
# Core region, so use the core pressure
1724+
# Core region, so use the core profiles
16901725
eqreg.pressure = self.pressure
1726+
eqreg.pprime = self.pprime
1727+
eqreg.fprime = self.fpolprime
16911728

16921729
region_objects[name] = eqreg
16931730
# The region objects need to be sorted, so that the
@@ -1741,8 +1778,17 @@ def fpol(self, psi):
17411778

17421779
@Equilibrium.handleMultiLocationArray
17431780
def fpolprime(self, psi):
1744-
"""psi-derivative of fpol"""
1745-
return self.fprime_spl(psi * self.f_psi_sign)
1781+
"""psi-derivative of fpol
1782+
Note: Zero outside core."""
1783+
fprime = self.fprime_spl(psi * self.f_psi_sign)
1784+
if self.psi_bdry is not None:
1785+
psinorm = (psi - self.psi_axis) / (self.psi_bdry - self.psi_axis)
1786+
if np.isscalar(psi):
1787+
if psinorm > 1.0:
1788+
fprime = 0.0
1789+
else:
1790+
fprime[psinorm > 1.0] = 0.0
1791+
return fprime
17461792

17471793
@Equilibrium.handleMultiLocationArray
17481794
def pressure(self, psi):
@@ -1751,6 +1797,21 @@ def pressure(self, psi):
17511797
return None
17521798
return self.p_spl(psi * self.f_psi_sign)
17531799

1800+
@Equilibrium.handleMultiLocationArray
1801+
def pprime(self, psi):
1802+
"""psi-derivative of plasma pressure
1803+
Note: Zero outside the core"""
1804+
if self.pprime_spl is None:
1805+
return None
1806+
pprime = self.pprime_spl(psi * self.f_psi_sign)
1807+
if self.psi_bdry is not None:
1808+
psinorm = (psi - self.psi_axis) / (self.psi_bdry - self.psi_axis)
1809+
if np.isscalar(psi) and psinorm > 1.0:
1810+
pprime = 0.0
1811+
else:
1812+
pprime[psinorm > 1.0] = 0.0
1813+
return pprime
1814+
17541815
@property
17551816
def Bt_axis(self):
17561817
"""Calculate toroidal field on axis"""
@@ -1815,6 +1876,8 @@ def read_geqdsk(
18151876

18161877
pressure = data["pres"]
18171878
fpol = data["fpol"]
1879+
fprime = data["ffprime"] / fpol
1880+
pprime = data["pprime"]
18181881

18191882
# Call __new__() first in case there is an exception in __init__(), we can still
18201883
# return a partially-initialised TokamakEquilibrium object
@@ -1840,6 +1903,8 @@ def read_geqdsk(
18401903
psi_bdry_gfile=psi_bdry_gfile,
18411904
psi_axis_gfile=psi_axis_gfile,
18421905
pressure=pressure,
1906+
fprime=fprime,
1907+
pprime=pprime,
18431908
wall=wall,
18441909
make_regions=make_regions,
18451910
settings=settings,

hypnotoad/core/mesh.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,27 @@ def geometry1(self):
926926

927927
self.Bxy = numpy.sqrt(self.Bpxy**2 + self.Btxy**2)
928928

929+
if hasattr(
930+
self.meshParent.equilibrium.regions[self.equilibriumRegion.name], "pprime"
931+
) and hasattr(
932+
self.meshParent.equilibrium.regions[self.equilibriumRegion.name], "fprime"
933+
):
934+
# Calculate parallel current density from p' and f'
935+
# Note: COCOS +1 convention is assumed
936+
# so mu0 * Jpar = -B f' - mu0 p' f / B
937+
938+
pprime = self.meshParent.equilibrium.regions[
939+
self.equilibriumRegion.name
940+
].pprime(self.psixy)
941+
fprime = self.meshParent.equilibrium.regions[
942+
self.equilibriumRegion.name
943+
].fprime(self.psixy)
944+
945+
mu0 = 4e-7 * numpy.pi
946+
self.Jpar0 = -(
947+
self.Bxy * fprime / mu0 + self.Rxy * self.Btxy * pprime / self.Bxy
948+
)
949+
929950
def geometry2(self):
930951
"""
931952
Continuation of geometry1(), but needs neighbours to have calculated Bp so called
@@ -3825,9 +3846,12 @@ def addFromRegionsXArray(name):
38253846
addFromRegions("bxcvy")
38263847
addFromRegions("bxcvz")
38273848

3828-
if hasattr(next(iter(self.equilibrium.regions.values())), "pressure"):
3849+
if hasattr(next(iter(self.regions.values())), "pressure"):
38293850
addFromRegions("pressure")
38303851

3852+
if hasattr(next(iter(self.regions.values())), "Jpar0"):
3853+
addFromRegions("Jpar0")
3854+
38313855
# Penalty mask
38323856
self.penalty_mask = BoutArray(
38333857
numpy.zeros((self.nx, self.ny)),

hypnotoad/geqdsk/_geqdsk.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from datetime import date
2424
from numpy import zeros, pi
25+
import numpy as np
2526

2627
from ._fileutils import f2s, ChunkOutput, write_1d, write_2d, next_value
2728

@@ -48,6 +49,11 @@ def write(data, fh, label=None, shot=None, time=None):
4849
4950
psi 2D array (nx,ny) of poloidal flux
5051
52+
ffprime 1D array of f(psi) * f'(psi). If not present
53+
then this is calculated from fpol
54+
pprime 1D array of p'(psi). If not present then
55+
this is calculated from pres
56+
5157
fh - file handle
5258
5359
label - Text label to put in the file
@@ -120,11 +126,7 @@ def write(data, fh, label=None, shot=None, time=None):
120126
f2s(data["zmagx"]) + f2s(0.0) + f2s(data["sibdry"]) + f2s(0.0) + f2s(0.0) + "\n"
121127
)
122128

123-
# SCENE has actual ff' and p' data so can use that
124129
# fill arrays
125-
# Lukas Kripner (16/10/2018): uncommenting this, since you left there
126-
# check for data existence bellow. This seems to as safer variant.
127-
workk = zeros([nx])
128130

129131
# Write arrays
130132
co = ChunkOutput(fh)
@@ -134,11 +136,29 @@ def write(data, fh, label=None, shot=None, time=None):
134136
if "ffprime" in data:
135137
write_1d(data["ffprime"], co)
136138
else:
137-
write_1d(workk, co)
139+
psi1D = np.linspace(data["simagx"], data["sibdry"], nx)
140+
from scipy import interpolate
141+
142+
sign = -1.0 if psi1D[1] < psi1D[0] else 1.0
143+
# spline fitting requires increasing X axis, so reverse if needed
144+
145+
fprime_spl = interpolate.InterpolatedUnivariateSpline(
146+
sign * psi1D, sign * data["fpol"]
147+
).derivative()
148+
ffprime = data["fpol"] * fprime_spl(sign * psi1D)
149+
write_1d(ffprime, co)
150+
138151
if "pprime" in data:
139152
write_1d(data["pprime"], co)
140153
else:
141-
write_1d(workk, co)
154+
psi1D = np.linspace(data["simagx"], data["sibdry"], nx)
155+
from scipy import interpolate
156+
157+
sign = -1.0 if psi1D[1] < psi1D[0] else 1.0
158+
pprime_spl = interpolate.InterpolatedUnivariateSpline(
159+
sign * psi1D, sign * data["pres"]
160+
).derivative()
161+
write_1d(pprime_spl(sign * psi1D), co)
142162

143163
write_2d(data["psi"], co)
144164
write_1d(data["qpsi"], co)
@@ -195,6 +215,8 @@ def read(fh, cocos=1):
195215
196216
fpol 1D array of f(psi)=R*Bt [meter-Tesla]
197217
pres 1D array of p(psi) [Pascals]
218+
ffprime 1D array of ff'(psi)
219+
pprime 1D array of p'(psi)
198220
qpsi 1D array of q(psi)
199221
200222
psi 2D array (nx,ny) of poloidal flux

0 commit comments

Comments
 (0)