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

1import typing 

2 

3import matplotlib.axes 

4import numpy as np 

5import numpy.typing as npt 

6 

7from ..gsfit import Gsfit 

8from .filament_corners import filament_corners 

9 

10 

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. 

18 

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`). 

23 

24 **This plotter is specific to ST40's experimental MDSplus database.** 

25 

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 

33 

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") 

39 

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) 

47 

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")) 

55 

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] 

62 

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 ) 

71 

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" 

89 

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" 

97 

98 ax.fill( 

99 corner_r, 

100 corner_z, 

101 facecolor=facecolor, 

102 edgecolor=edgecolor, 

103 linewidth=1.0, 

104 zorder=2, 

105 )