Coverage for python/gsfit/plotting/two_d_tiles.py: 0%
42 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-14 15:25 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-14 15:25 +0000
1import typing
3import matplotlib.axes
4import numpy as np
5import numpy.typing as npt
7from ..gsfit import Gsfit
8from .filament_corners import filament_corners
11def plot(
12 gsfit_controller: Gsfit,
13 ax: matplotlib.axes.Axes,
14 time_desired: float | None = None,
15) -> None:
16 """
17 Plot the plasma facing tiles.
19 The tile geometry is not stored in any IDS, so it is read directly from ST40's ELMAG MDSplus tree
20 under `LIMITER.TILES`, using the `elmag` workflow of the selected `database_reader`. The tiles are
21 (possibly sheared) parallelograms; the corner construction and colours follow the ST40 physics
22 viewer (`plot_elmag.py`).
24 **This plotter is specific to ST40's experimental MDSplus database.**
26 :param gsfit_controller: the Gsfit controller, whose `pulseNo` and settings select the ELMAG tree
27 :param ax: the matplotlib axes to draw on
28 :param time_desired: unused; kept for API symmetry with the other `two_d_*` plotters
29 """
30 # `st40_database` is only installed with the `with_st40_mdsplus` extra, and every plotter is
31 # imported by `gsfit.plotting`, so it is imported here rather than at the top of the file
32 from st40_database import GetData
34 database_reader_settings = gsfit_controller.settings["GSFIT_code_settings.json"]["database_reader"]
35 database_reader_method = database_reader_settings["method"]
36 workflow = database_reader_settings.get(database_reader_method, {}).get("workflow", {})
37 if "elmag" not in workflow:
38 raise ValueError(f"The tiles are read from ST40's ELMAG tree, but database_reader={database_reader_method} has no `elmag` workflow")
40 # `pulseNo = null` means "use the shot's pulse"
41 if workflow["elmag"]["pulseNo"] is None:
42 elmag_pulseNo = gsfit_controller.pulseNo
43 else:
44 elmag_pulseNo = workflow["elmag"]["pulseNo"]
45 elmag_run_name = workflow["elmag"]["run_name"]
46 elmag = GetData(elmag_pulseNo, f"ELMAG#{elmag_run_name}", is_fail_quiet=False)
48 tiles_r = typing.cast(npt.NDArray[np.float64], elmag.get("LIMITER.TILES.R")) # [metre]
49 tiles_z = typing.cast(npt.NDArray[np.float64], elmag.get("LIMITER.TILES.Z")) # [metre]
50 tiles_d_r = typing.cast(npt.NDArray[np.float64], elmag.get("LIMITER.TILES.DR")) # [metre]
51 tiles_d_z = typing.cast(npt.NDArray[np.float64], elmag.get("LIMITER.TILES.DZ")) # [metre]
52 tiles_angle_1 = typing.cast(npt.NDArray[np.float64], elmag.get("LIMITER.TILES.ANGLE1")) # [radian]
53 tiles_angle_2 = typing.cast(npt.NDArray[np.float64], elmag.get("LIMITER.TILES.ANGLE2")) # [radian]
54 tile_names = typing.cast(list[str], elmag.get("LIMITER.TILES.NAMES"))
56 # The geometry can have more entries than there are names: ELMAG#RUN16 has 54 geometries but
57 # 53 names, and the unnamed last geometry is a copy of `MCB1`. Only the named tiles are drawn,
58 # which is also what the physics viewer ends up drawing
59 n_tiles: int = len(tile_names)
60 for i_tile in range(n_tiles):
61 tile_name: str = tile_names[i_tile]
63 corner_r, corner_z = filament_corners(
64 filament_r=tiles_r[i_tile],
65 filament_z=tiles_z[i_tile],
66 filament_d_r=tiles_d_r[i_tile],
67 filament_d_z=tiles_d_z[i_tile],
68 filament_angle_1=tiles_angle_1[i_tile],
69 filament_angle_2=tiles_angle_2[i_tile],
70 )
72 # Colours match `plot_elmag.py`
73 facecolor: str
74 if "INNER" in tile_name:
75 # Inner divertor tiles
76 facecolor = "#657B00"
77 elif "OUTER" in tile_name:
78 # Outer divertor tiles
79 facecolor = "#810D8E"
80 elif "CP" in tile_name:
81 # Centre post tiles
82 facecolor = "#4C7478"
83 elif "MCT" in tile_name or "MCB" in tile_name:
84 # Merging-compression tiles, top and bottom
85 facecolor = "#855784"
86 else:
87 # High field side tiles and molybdenum plates
88 facecolor = "#CF9966"
90 # The molybdenum plates are too thin to see when only filled, so they are also outlined.
91 # The other tiles have no edge, so that they are not drawn larger than reality
92 edgecolor: str
93 if "MOLY" in tile_name:
94 edgecolor = facecolor
95 else:
96 edgecolor = "none"
98 ax.fill(
99 corner_r,
100 corner_z,
101 facecolor=facecolor,
102 edgecolor=edgecolor,
103 linewidth=1.0,
104 zorder=2,
105 )