Skip to content

Commit d139331

Browse files
authored
More generic x-point check (#4172)
* More general x-point lcfs check in finder * Rename and remove repetition * add array use
1 parent 7c92658 commit d139331

File tree

2 files changed

+99
-20
lines changed

2 files changed

+99
-20
lines changed

bluemira/equilibria/find.py

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@
2222
from bluemira.base.look_and_feel import bluemira_warn
2323
from bluemira.equilibria.constants import B_TOLERANCE, X_TOLERANCE
2424
from bluemira.equilibria.error import EquilibriaError
25-
from bluemira.geometry.coordinates import Coordinates, get_area_2d, in_polygon
25+
from bluemira.geometry.coordinates import (
26+
Coordinates,
27+
get_area_2d,
28+
in_polygon,
29+
join_intersect,
30+
)
2631
from bluemira.utilities.tools import floatify
2732

2833
if TYPE_CHECKING:
@@ -819,13 +824,15 @@ def get_flux_loop(psi_norm):
819824
if o_points is None or x_points is None:
820825
o_points, x_points = find_OX_points(x, z, psi)
821826

827+
primary_xp, primary_op = x_points[0], o_points[0]
822828
delta = high - low
823829

824830
while delta > psi_n_tol:
825831
middle = low + delta / 2
826832
flux_surface = get_flux_loop(middle)
827833

828-
if flux_surface.closed:
834+
ixp = x_point_check(flux_surface, primary_op, primary_xp)
835+
if flux_surface.closed and ixp is None:
829836
# Middle flux surface is still closed, shift search bounds
830837
low = middle
831838

@@ -869,6 +876,86 @@ def get_flux_loop(psi_norm):
869876
return lcfs, separatrix
870877

871878

879+
def x_point_check(flux_surface: Coordinates, op: Opoint, xp: Xpoint):
880+
"""
881+
Check if there are intersections of a flux surface with the
882+
line tangent to the o-point-x-point vector at the x-point.
883+
884+
Parameters
885+
----------
886+
flux_surface:
887+
Flux surface of interest, e.g., candidate closed
888+
flux surfaces when finding LCFS
889+
op:
890+
Primary o-point for an equilibrium
891+
xp:
892+
X-point of interest, usually primary
893+
894+
Returns
895+
-------
896+
arg_inters:
897+
Intersection indices
898+
"""
899+
length = np.hypot(np.max(flux_surface.x), np.max(np.abs(flux_surface.z)))
900+
tanget_line = two_point_angled_line(op, xp, length) # default theta is tangent
901+
_, arg_inters = join_intersect(flux_surface, tanget_line, get_arg=True)
902+
return arg_inters.sort()
903+
904+
905+
def two_point_angled_line(
906+
centre_point: PsiPoint | Coordinates | np.ndarray,
907+
edge_point: PsiPoint | Coordinates | np.ndarray,
908+
length: float,
909+
theta: float = np.pi / 2,
910+
):
911+
"""
912+
Make a Coordinate object for a line of a given length that is at a given
913+
angle (default is tangent) to a surface with a reference radial vector
914+
specified by two points.
915+
916+
Parameters
917+
----------
918+
centre_point:
919+
Start point of reference vector
920+
edge_point:
921+
End point of reference vector, on the surface
922+
where we will take the tangent.
923+
length:
924+
Length to make the tangent line Coordinate
925+
theta:
926+
CCW angle in radians
927+
928+
Returns
929+
-------
930+
:
931+
Coordinates of the tangent line with length=length
932+
"""
933+
cp = (
934+
np.array([[centre_point.x], [0.0], [centre_point.z]])
935+
if isinstance(centre_point, PsiPoint | Coordinates)
936+
else centre_point
937+
)
938+
tp = (
939+
np.array([[edge_point.x], [0.0], [edge_point.z]])
940+
if isinstance(edge_point, PsiPoint | Coordinates)
941+
else edge_point
942+
)
943+
a = cp - tp
944+
a_hat = a / np.linalg.norm(a)
945+
946+
rot_matrix = np.array([
947+
[np.cos(theta), 0, -np.sin(theta)],
948+
[0, 0, 0],
949+
[np.sin(theta), 0, np.cos(theta)],
950+
]) # ccw rotation about y-axis
951+
952+
b_hat = rot_matrix @ a_hat
953+
954+
p1 = tp - b_hat * length / 2
955+
p2 = tp + b_hat * length / 2
956+
return Coordinates(np.array([p1, p2]).T)
957+
958+
872959
def grid_2d_contour(x: np.ndarray, z: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
873960
"""
874961
Grid a smooth contour and get the outline of the cells it encompasses.

eudemo/eudemo/ivc/divertor_silhouette.py

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
from bluemira.base.parameter_frame import Parameter, ParameterFrame
2121
from bluemira.base.reactor import ComponentManager
2222
from bluemira.builders.divertor import DivertorBuilder
23-
from bluemira.equilibria.find import find_flux_surface_through_point
23+
from bluemira.equilibria.find import (
24+
find_flux_surface_through_point,
25+
two_point_angled_line,
26+
)
2427
from bluemira.equilibria.find_legs import LegFlux
2528
from bluemira.geometry.tools import (
2629
interpolate_bspline,
@@ -215,33 +218,22 @@ def _make_angled_target(self, leg: LegPosition):
215218
# i.e. towards the increasing leg length
216219
post_target_point = sol.value_at(distance=self.leg_length[leg].value + 0.1)
217220

218-
a = post_target_point - target_point
219-
a_hat = a / np.linalg.norm(a)
220-
221221
# ccw angle
222222
theta = (
223223
np.deg2rad(self.params.div_targ_angle_ib.value)
224224
if leg is LegPosition.INNER
225225
else np.deg2rad(self.params.div_targ_angle_ob.value)
226226
)
227227

228-
rot_matrix = np.array([
229-
[np.cos(theta), 0, -np.sin(theta)],
230-
[0, 0, 0],
231-
[np.sin(theta), 0, np.cos(theta)],
232-
]) # ccw rotation about y-axis
233-
234-
b_hat = rot_matrix @ a_hat
235-
236-
target_half_length = (
237-
0.5 * self.params.div_Ltarg_ib.value
228+
target_length = (
229+
self.params.div_Ltarg_ib.value
238230
if leg is LegPosition.INNER
239-
else 0.5 * self.params.div_Ltarg_ob.value
231+
else self.params.div_Ltarg_ob.value
240232
)
241233

242-
p1 = target_point - b_hat * target_half_length
243-
p2 = target_point + b_hat * target_half_length
244-
return np.array([p1, p2]).T
234+
return two_point_angled_line(
235+
post_target_point, target_point, target_length, theta
236+
)
245237

246238
def make_dome(self, start: np.ndarray, end: np.ndarray, label: str) -> BluemiraWire:
247239
"""

0 commit comments

Comments
 (0)