|
22 | 22 | from bluemira.base.look_and_feel import bluemira_warn |
23 | 23 | from bluemira.equilibria.constants import B_TOLERANCE, X_TOLERANCE |
24 | 24 | 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 | +) |
26 | 31 | from bluemira.utilities.tools import floatify |
27 | 32 |
|
28 | 33 | if TYPE_CHECKING: |
@@ -819,13 +824,15 @@ def get_flux_loop(psi_norm): |
819 | 824 | if o_points is None or x_points is None: |
820 | 825 | o_points, x_points = find_OX_points(x, z, psi) |
821 | 826 |
|
| 827 | + primary_xp, primary_op = x_points[0], o_points[0] |
822 | 828 | delta = high - low |
823 | 829 |
|
824 | 830 | while delta > psi_n_tol: |
825 | 831 | middle = low + delta / 2 |
826 | 832 | flux_surface = get_flux_loop(middle) |
827 | 833 |
|
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: |
829 | 836 | # Middle flux surface is still closed, shift search bounds |
830 | 837 | low = middle |
831 | 838 |
|
@@ -869,6 +876,86 @@ def get_flux_loop(psi_norm): |
869 | 876 | return lcfs, separatrix |
870 | 877 |
|
871 | 878 |
|
| 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 | + |
872 | 959 | def grid_2d_contour(x: np.ndarray, z: np.ndarray) -> tuple[np.ndarray, np.ndarray]: |
873 | 960 | """ |
874 | 961 | Grid a smooth contour and get the outline of the cells it encompasses. |
|
0 commit comments