Coverage for python/gsfit/database_readers/st40_mdsplus_with_digital_filter/setup_pulse_schedule.py: 0%
21 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
2from typing import TYPE_CHECKING
4import numpy as np
5import numpy.typing as npt
6from gsfit_rs import PulseSchedule
7from st40_database import GetData
9if TYPE_CHECKING:
10 from . import DatabaseReader
13def setup_pulse_schedule(
14 self: "DatabaseReader",
15 pulseNo: int,
16 settings: dict[str, typing.Any],
17) -> PulseSchedule:
18 """
19 This method initialises the Rust `PulseSchedule` class, which holds an IMAS `pulse_schedule` IDS.
21 :param pulseNo: Pulse number, used to read from the database
22 :param settings: Dictionary containing the JSON settings read from the `settings` directory
24 **This method is specific to ST40's experimental MDSplus database, with a digital filter.**
26 Only the gap definitions are filled, `pulse_schedule/position_control/gap(i)/name`, `.../r`, `.../z` and `.../angle`.
27 `solve_grad_shafranov` copies them onto the equilibrium IDS and calculates the value of each gap.
29 **The angle is converted.**
30 ELMAG stores `GAPS.ANGLE` counter-clockwise from the outward major radius, in the usual plot with `R` to the right and `Z` upwards.
31 For example `MCTGAP`, on the upper merging-compression tile, is at 3.8 radian, which points down and inwards, into the plasma.
32 GSFit stores the angle in the equilibrium IDS's convention, clockwise from `grad(R)`, so it is negated here.
34 See `python/gsfit/database_readers/interface.py` for more details on how a new database_reader should be implemented.
35 """
37 elmag_run_name = settings["GSFIT_code_settings.json"]["database_reader"]["st40_mdsplus_with_digital_filter"]["workflow"]["elmag"]["run_name"]
38 elmag = GetData(pulseNo, f"ELMAG#{elmag_run_name}", is_fail_quiet=False)
39 gaps_angle_counter_clockwise = typing.cast(npt.NDArray[np.float64], elmag.get("GAPS.ANGLE")) # [radian]
40 gaps_name = typing.cast(list[str], elmag.get("GAPS.NAMES"))
41 gaps_r = typing.cast(npt.NDArray[np.float64], elmag.get("GAPS.R_ORIGIN")) # [metre]
42 gaps_z = typing.cast(npt.NDArray[np.float64], elmag.get("GAPS.Z_ORIGIN")) # [metre]
44 # Counter-clockwise to clockwise, kept within [0, 2 * pi) as ELMAG stores it
45 gaps_angle = np.mod(-gaps_angle_counter_clockwise, 2.0 * np.pi) # [radian]
47 n_gaps = len(gaps_name)
48 if not (len(gaps_angle) == len(gaps_r) == len(gaps_z) == n_gaps):
49 raise ValueError(
50 f"ELMAG#{elmag_run_name} GAPS: the nodes disagree on the number of gaps; "
51 f"NAMES={n_gaps}, ANGLE={len(gaps_angle)}, R_ORIGIN={len(gaps_r)}, Z_ORIGIN={len(gaps_z)}"
52 )
54 # Initialise the PulseSchedule Rust class
55 pulse_schedule = PulseSchedule()
57 # A run with no gaps leaves the pulse schedule without any, which is allowed
58 for i_gap in range(n_gaps):
59 pulse_schedule.add_gap(
60 name=gaps_name[i_gap],
61 r=float(gaps_r[i_gap]),
62 z=float(gaps_z[i_gap]),
63 angle=float(gaps_angle[i_gap]),
64 )
66 return pulse_schedule