Skip to content

Commit 0155119

Browse files
authored
Merge pull request #112 from ProjectTorreyPines/sol_fixes
Sol fixes
2 parents 3e721b6 + 66cf4bf commit 0155119

File tree

2 files changed

+202
-28
lines changed

2 files changed

+202
-28
lines changed

src/physics/fluxsurfaces.jl

Lines changed: 190 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -180,26 +180,86 @@ end
180180

181181

182182
"""
183-
find_psi_2nd_separatrix(eqt::IMAS.equilibrium__time_slice, PSI_interpolant::Interpolations.AbstractInterpolation)
183+
find_psi_2nd_separatrix(eqt::IMAS.equilibrium__time_slice)
184184
185-
Returns psi of the second magentic separatrix. This relies on the fact that find_x_points! saves the x points in such a way
186-
that the last one is the null with Z opposite to the first x point which is the closest in psi to the lcfs.
185+
Returns psi of the second magentic separatrix. This relies only on eqt and finds the 2nd sep geometrically.
187186
"""
188-
function find_psi_2nd_separatrix(eqt::IMAS.equilibrium__time_slice, PSI_interpolant::Interpolations.AbstractInterpolation)
189-
psi2nd = PSI_interpolant.(eqt.boundary.x_point[end].r, eqt.boundary.x_point[end].z)
190-
return psi2nd * 0.999 .+ eqt.profiles_1d.psi[end] * 0.001
187+
function find_psi_2nd_separatrix(eqt::IMAS.equilibrium__time_slice)
188+
189+
ZA = eqt.global_quantities.magnetic_axis.z
190+
191+
_, psi_separatrix = find_psi_boundary(eqt; raise_error_on_not_open=true) # psi first open flux surfce up to precision
192+
surface, _ = flux_surface(eqt, psi_separatrix, :open)
193+
194+
# First check if we are in a double null configuration
195+
for (r,z) in surface
196+
if isempty(r) || all(z .> ZA) || all(z .< ZA)
197+
continue
198+
end
199+
if z[1]*z[end] < 0
200+
#if double null, all open surfaces in the SOL start and finish in opposite sides of the midplane
201+
return psi_separatrix
202+
end
203+
end
204+
205+
# Single null case
206+
eqt2d = findfirst(:rectangular, eqt.profiles_2d)
207+
psi_axis = eqt.profiles_1d.psi[1] # psi value on axis
208+
psi_sign = sign(psi_separatrix - psi_axis) # +1 for increasing psi / -1 for decreasing psi
209+
psi_low = psi_separatrix
210+
if psi_sign >0
211+
# increasing psi
212+
psi_up = maximum(eqt2d.psi)
213+
else
214+
# decresing psi
215+
psi_up = minimum(eqt2d.psi)
216+
end
217+
psi = (psi_up + psi_low) / 2.0
218+
err = Inf
219+
counter = 0
220+
counter_max = 50
221+
while abs(err) > 1E-7 && counter < counter_max
222+
surface, _ = flux_surface(eqt, psi, :open)
223+
for (r, z) in surface
224+
225+
if isempty(r) || all(z .> ZA) || all(z .< ZA)
226+
continue
227+
end
228+
229+
if z[end] * z[1] > 0
230+
# the surface starts and finishes on the same side of the midplane; aka is diverted
231+
psi_low = psi
232+
else
233+
# the surface starts and finishes on opposite sides of the midplane
234+
psi_up = psi
235+
end
236+
237+
end
238+
239+
if err == abs(psi_up - psi_low)/abs(psi_low)
240+
# neither psi_up nor psi_low were updated; all surfaces in surface were discarded
241+
# update psi_up, because psi_low is intrinsically safe for the algorithm
242+
psi_up = psi
243+
end
244+
245+
# update new error
246+
err = abs(psi_up - psi_low)/abs(psi_low)
247+
psi = (psi_up + psi_low) / 2.0
248+
249+
counter = counter + 1
250+
end
251+
252+
return psi_up
191253
end
192254

193255
"""
194256
find_psi_2nd_separatrix(dd::IMAS.dd)
195257
196-
Returns psi of the second magentic separatrix. This relies on the fact that find_x_points! saves the x points in such a way
197-
that the last one is the null with Z opposite to the first x point which is the closest in psi to the lcfs
258+
Returns psi of the second magentic separatrix. This relies only on eqt and finds the 2nd sep geometrically.
198259
"""
199260

200261
function find_psi_2nd_separatrix(dd::IMAS.dd)
201-
r, z, PSI_interpolant = ψ_interpolant(dd.equilibrium.time_slice[].profiles_2d)
202-
return find_psi_2nd_separatrix(dd.equilibrium.time_slice[], PSI_interpolant)
262+
return find_psi_2nd_separatrix(dd.equilibrium.time_slice[])
203263
end
204264

205265
"""
@@ -235,7 +295,7 @@ function find_psi_last_diverted(
235295
sign_z = sign(Xpoint2[2]) # sign of Z coordinate of 2nd null
236296

237297
_, psi_separatrix = find_psi_boundary(eqt; raise_error_on_not_open=true) # psi LCFS
238-
psi_2ndseparatrix = find_psi_2nd_separatrix(eqt, PSI_interpolant) # psi second magnetic separatrix
298+
psi_2ndseparatrix = find_psi_2nd_separatrix(eqt) # psi second magnetic separatrix
239299
crossings = intersection([RA, maximum(wall_r)], [ZA, ZA], wall_r, wall_z)[2] # (r,z) point of intersection btw outer midplane (OMP) with wall
240300
r_wall_midplane = [cr[1] for cr in crossings] # R coordinate of the wall at OMP
241301
psi_wall_midplane = PSI_interpolant.(r_wall_midplane, ZA)[1] # psi at the intersection between wall and omp
@@ -360,6 +420,124 @@ function find_psi_last_diverted(dd::IMAS.dd; precision::Float64=1e-7)
360420
return find_psi_last_diverted(dd.equilibrium.time_slice[], dd.wall, PSI_interpolant; precision)
361421
end
362422

423+
"""
424+
find_psi_max(
425+
eqt::IMAS.equilibrium__time_slice,
426+
precision::Float64=1e-3)
427+
428+
Returns the max psi useful for an ofl in the SOL with no wall.
429+
"""
430+
function find_psi_max(
431+
eqt::IMAS.equilibrium__time_slice;
432+
precision::Float64=1e-2)
433+
434+
eqt2d = findfirst(:rectangular, eqt.profiles_2d)
435+
RA = eqt.global_quantities.magnetic_axis.r
436+
ZA = eqt.global_quantities.magnetic_axis.z
437+
438+
psi_2ndseparatrix = find_psi_2nd_separatrix(eqt)
439+
psi_axis = eqt.profiles_1d.psi[1] # psi value on axis
440+
psi_sign = sign(psi_2ndseparatrix - psi_axis) # +1 for increasing psi / -1 for decreasing psi
441+
442+
# find the two surfaces `psi_first_lfs_far` and `psi_last_lfs` around the last diverted flux surface
443+
counter_max = 50
444+
counter = 0
445+
if psi_sign > 0
446+
# increasing psi
447+
psi_up = maximum(eqt2d.psi)
448+
else
449+
# decresing psi
450+
psi_up = minimum(eqt2d.psi)
451+
end
452+
453+
# check first if psi_up is already the psi we want
454+
surface, _ = flux_surface(eqt, psi_up, :open)
455+
for (r,z) in surface
456+
457+
if isempty(r) || all(z .> ZA) || all(z .< ZA)
458+
continue
459+
end
460+
461+
_, crossings = intersection(r, z, [1, 10]*RA, [1, 1]*ZA) # find intersection with midplane
462+
463+
if isempty(crossings)
464+
continue
465+
end
466+
467+
rr = crossings[1][1] # R coordiante of intersection
468+
zz = crossings[1][2] # Z coordiante of intersection
469+
470+
if rr < RA
471+
# surface is in :hfs
472+
continue
473+
end
474+
475+
if z[1]*z[end] < 0
476+
#surface starts and finishes in opposite sides of the midplane is the surface we want
477+
return psi_up
478+
end
479+
480+
end
481+
482+
# psi_up corresponds to a surface that does not start and finish on opposite sides of the midplane
483+
psi_low = psi_2ndseparatrix # psi_low starts and finishes on opposite sides of the midlpane
484+
psi = (psi_up + psi_low) / 2.0
485+
zmax = maximum(eqt2d.grid.dim2)
486+
zmin = minimum(eqt2d.grid.dim2)
487+
488+
err = Inf
489+
while abs(err) > precision && counter < counter_max
490+
surface, _ = flux_surface(eqt, psi, :open)
491+
for (r, z) in surface
492+
493+
if isempty(r) || all(z .> ZA) || all(z .< ZA)
494+
continue
495+
end
496+
497+
_, crossings = intersection(r, z, [1, 10]*RA, [1, 1]*ZA) # find intersection of surface with midplane
498+
499+
if isempty(crossings)
500+
continue
501+
end
502+
503+
rr = crossings[1][1] # R coordiante of intersection
504+
zz = crossings[1][2] # Z coordiante of intersection
505+
506+
if rr < RA
507+
# surface is in :hfs
508+
continue
509+
end
510+
511+
if z[end] * z[1] < 0
512+
# psi starts and finishes on opposite sides of the midplane
513+
if abs(z[end] * z[1] - zmax * zmin)/(zmax^2) < 0.1
514+
# make sure that indeed the surface goes from zmin to zmax, or close to that
515+
psi_low = psi
516+
else
517+
psi_up = psi
518+
end
519+
else
520+
# psi does not start and finish on opposite sides of the midplane
521+
psi_up = psi
522+
end
523+
end
524+
525+
if err == abs(psi_up - psi_low)/abs(psi_low)
526+
# neither psi_up nor psi_low were updated; all surfaces in surface were discarded
527+
# update psi_up, because psi_low is intrinsically safe for the algorithm
528+
psi_up = psi
529+
end
530+
531+
err = abs(psi_up - psi_low)/abs(psi_low)
532+
psi = (psi_up + psi_low) / 2.0
533+
534+
counter = counter + 1
535+
end
536+
537+
538+
return psi_low
539+
end
540+
363541

364542
"""
365543
interp_rmid_at_psi(eqt::IMAS.equilibrium__time_slice, PSI_interpolant::Interpolations.AbstractInterpolation, R::AbstractVector{<:Real})
@@ -736,7 +914,7 @@ function flux_surfaces(eqt::equilibrium__time_slice{T}, B0::T, R0::T; upsample_f
736914

737915
# secondary separatrix
738916
if length(eqt.boundary.x_point) > 1
739-
psi2nd = find_psi_2nd_separatrix(eqt, PSI_interpolant)
917+
psi2nd = find_psi_2nd_separatrix(eqt)
740918
tmp, _ = flux_surface(r, z, PSI, eqt.profiles_1d.psi, eqt.global_quantities.magnetic_axis.r, eqt.global_quantities.magnetic_axis.z, psi2nd, :encircling)
741919
if !isempty(tmp)
742920
(pr2nd, pz2nd) = tmp[1]

src/physics/sol.jl

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -143,20 +143,22 @@ function sol(eqt::IMAS.equilibrium__time_slice, wall_r::Vector{T}, wall_z::Vecto
143143
psi__axis_level = eqt.profiles_1d.psi[1] # psi value on axis
144144
_, psi__boundary_level = find_psi_boundary(eqt; raise_error_on_not_open=true) # find psi at LCFS
145145
# find psi at second magnetic separatrix
146-
psi__2nd_separatix = find_psi_2nd_separatrix(eqt, PSI_interpolant) # find psi at 2nd magnetic separatrix
146+
psi__2nd_separatix = find_psi_2nd_separatrix(eqt) # find psi at 2nd magnetic separatrix
147147
psi_sign = sign(psi__boundary_level - psi__axis_level) # sign of the poloidal flux taking psi_axis = 0
148148
if !isempty(wall_r)
149149
# SOL with wall
150150
crossings = intersection([RA, maximum(wall_r)], [ZA, ZA], wall_r, wall_z)[2] # (r,z) point of intersection btw outer midplane (OMP) with wall
151151
r_wall_midplane = [cr[1] for cr in crossings] # R coordinate of the wall at OMP
152152
psi_wall_midplane = PSI_interpolant.(r_wall_midplane, ZA)[1] # psi at the intersection between wall and omp
153153
psi_last_lfs, psi_first_lfs_far, null_within_wall = find_psi_last_diverted(eqt, wall_r, wall_z, PSI_interpolant) # find psi at LDFS
154+
threshold = (psi_last_lfs + psi_first_lfs_far) / 2.0
154155
else
155156
# SOL without wall
156-
psi_wall_midplane = maximum(psi_sign .* eqt2d.psi) - psi_sign # if no wall, upper bound of psi is maximum value in eqt -1 (safe)
157+
psi_wall_midplane = find_psi_max(eqt)
157158
psi_last_lfs = psi__boundary_level
158159
psi_first_lfs_far = psi__boundary_level .+ 1E-5
159160
null_within_wall = true
161+
threshold = psi__2nd_separatix
160162
end
161163
############
162164

@@ -205,26 +207,20 @@ function sol(eqt::IMAS.equilibrium__time_slice, wall_r::Vector{T}, wall_z::Vecto
205207
# Add SOL surface in OFL_hfs
206208
ofl_type = :hfs
207209
else
208-
if isempty(wall_r)
209-
# if no wall, see if lines encircle the plasma
210-
if ofl.z[1] * ofl.z[end] > 0
210+
if ofl.z[1] * ofl.z[end] < 0
211+
# if z[1] and z[end] have different sign, for sure it is :lfs_far
211212
# Add SOL surface in OFL_lfs
212-
ofl_type = :lfs
213-
else
214-
# Add SOL surface in OFL_lfs_far
215213
ofl_type = :lfs_far
216-
end
217-
else
218-
# if use_wall, :lfs and :lfs_far are located based on a condition on psi
219-
threshold = (psi_last_lfs + psi_first_lfs_far) / 2.0
220-
if psi_sign * level <= psi_sign * threshold # psi_sign to account for increasing/decreasing psi
221-
# Add SOL surface in OFL_lfs
214+
elseif psi_sign * level < psi_sign * threshold
215+
# if z[1] and z[end] have same sign, check psi
216+
# Add SOL surface in OFL_lfs_far
217+
222218
ofl_type = :lfs
223219
else
224-
# Add SOL surface in OFL_lfs_far
220+
225221
ofl_type = :lfs_far
226222
end
227-
end
223+
228224
end
229225

230226
push!(OFL[ofl_type], ofl) # add result

0 commit comments

Comments
 (0)