Skip to content

Commit 4f899f1

Browse files
committed
Cleanup tracing
* Rename internal stuff with _ * Use OutOfDomainError if we leave the domain * Add doc strings
1 parent e5237a5 commit 4f899f1

File tree

1 file changed

+82
-15
lines changed

1 file changed

+82
-15
lines changed

xbout/tracing.py

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,19 @@
44
from warnings import warn
55

66

7-
def rz_to_ab(rz, mesh):
7+
class OutOfDomainError(ValueError):
8+
pass
9+
10+
11+
def _rz_to_ab(rz, mesh):
12+
"""
13+
This functions finds the position (R-z) in the mesh and
14+
returns the index and the relative coordinates [0,1] in that cell.
15+
It uses a newton iteration.
16+
"""
817
ij = mesh.find_cell(rz)
9-
assert ij >= 0, "We left the domain"
18+
if ij < 0:
19+
raise OutOfDomainError()
1020
_, nz = mesh.shape
1121
i, j = ij // nz, ij % nz
1222
ABCD = mesh.grid[i : i + 2, j : j + 2]
@@ -17,10 +27,12 @@ def rz_to_ab(rz, mesh):
1727
rz0 = rz - A
1828

1929
def fun(albe):
30+
"The forwards function"
2031
al, be = albe
2132
return rz0 - a * al - b * be - c * al * be
2233

2334
def J(albe):
35+
"The jacobian"
2436
al, be = albe
2537
return np.array([-a - c * be, -b - c * al])
2638

@@ -33,7 +45,12 @@ def J(albe):
3345
return albe, ij
3446

3547

36-
def ab_to_rz(ab, ij, mesh):
48+
def _ab_to_rz(ab, ij, mesh):
49+
"""
50+
Calculate the position in cartesian R-z coordinates
51+
given the relative position in the grid cell (ab) and
52+
the grid indices ij.
53+
"""
3754
_, nz = mesh.shape
3855
i, j = ij // nz, ij % nz
3956
A = mesh.grid[i, j]
@@ -44,18 +61,30 @@ def ab_to_rz(ab, ij, mesh):
4461
return A + al * a + be * b + al * be * c
4562

4663

47-
def setup_mesh(x, y):
48-
def per(d):
64+
def _setup_mesh(x, y):
65+
"""
66+
Setup the mesh and store some additional info.
67+
68+
The fci-mesh is assumed to periodic in z - but eudist does not
69+
handle this so we need to copy the first corners around.
70+
"""
71+
72+
def make_periodic(d):
73+
"The grid should be periodic in z"
4974
return np.concatenate((d, d[:, :1]), axis=1)
5075

5176
assert x.dims == y.dims
5277
assert x.dims == ("x", "z")
53-
x = per(x.data)
54-
y = per(y.data)
55-
return mymesh(x, y)
78+
x = make_periodic(x.data)
79+
y = make_periodic(y.data)
80+
return _MyMesh(x, y)
5681

5782

58-
class mymesh(eudist.PolyMesh):
83+
class _MyMesh(eudist.PolyMesh):
84+
"""
85+
Like the PolyMesh but with extra data
86+
"""
87+
5988
def __init__(self, x, y):
6089
super().__init__()
6190
self.r = x
@@ -65,20 +94,56 @@ def __init__(self, x, y):
6594

6695

6796
class Tracer:
97+
"""
98+
Use an EMC3-like tracing. This relies on the grid containing a
99+
tracing to the next slice. The current position in RZ coordinates
100+
is converted to the relative position in the grid, and then the
101+
reverse is done for the end of the "flux tube" defined by corners
102+
of the cells.
103+
"""
104+
68105
def __init__(self, ds, direction="forward"):
106+
"""
107+
ds: xr.Dataset
108+
a dataset with the needed FCI data from zoidberg.
109+
110+
direction: str
111+
"forward" or "backward"
112+
"""
69113
meshes = []
70114
for yi in range(len(ds.y)):
71115
dsi = ds.isel(y=yi)
72116
meshes.append(
73117
[
74-
setup_mesh(dsi[f"{pre}R"], dsi[f"{pre}Z"])
118+
_setup_mesh(dsi[f"{pre}R"], dsi[f"{pre}Z"])
75119
for pre in ["", f"{direction}_"]
76120
]
77121
+ [yi]
78122
)
79123
self.meshes = meshes
80124

81125
def poincare(self, rz, yind=0, num=100, early_exit="warn"):
126+
"""
127+
rz : array-like with 2 values
128+
The RZ position where to start tracing
129+
yind : int
130+
The y-index of the slice where to start tracing.
131+
num : int
132+
Number of rounds to trace for
133+
early_exit : str
134+
How to handle if we leave the domain before doing `num` rounds.
135+
The possible values are:
136+
"ignore" : do nothing
137+
"warn": raise a warning
138+
"plot" : try to plot the grid and where we leave
139+
"raise" : Raise the exception for handling by the caller
140+
141+
Returns
142+
-------
143+
np.array
144+
An array of shape (num, 2) or less then num if the tracing leaves the domain.
145+
It contains the r-z coordinates at the y-index where tracing started.
146+
"""
82147
rz = np.array(rz)
83148
assert rz.shape == (2,)
84149
thismeshes = self.meshes[yind:] + self.meshes[:yind]
@@ -88,7 +153,7 @@ def poincare(self, rz, yind=0, num=100, early_exit="warn"):
88153
for i in range(1, num):
89154
for d, meshes in enumerate(thismeshes):
90155
try:
91-
abij = rz_to_ab(rz, meshes[0])
156+
abij = _rz_to_ab(rz, meshes[0])
92157
except AssertionError as e:
93158
if early_exit == "warn":
94159
warn(f"early exit in iteration {i} because `{e}`")
@@ -105,11 +170,13 @@ def poincare(self, rz, yind=0, num=100, early_exit="warn"):
105170
elif early_exit == "raise":
106171
raise
107172
else:
108-
assert (
109-
early_exit == "ignore"
110-
), f'early_exit needs to be one of ["warn", "plot", "raise", ignore"] but got `{early_exit}`'
173+
assert early_exit == "ignore", (
174+
"early_exit needs to be one of "
175+
+ '["warn", "plot", "raise", ignore"] '
176+
+ f"but got `{early_exit}`"
177+
)
111178
return out[:i]
112-
rz = ab_to_rz(*abij, meshes[1])
179+
rz = _ab_to_rz(*abij, meshes[1])
113180
last = meshes
114181
out[i] = rz
115182
return out

0 commit comments

Comments
 (0)