Coverage for python/gsfit/database_readers/interface.py: 100%
34 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 Protocol
4import numpy as np
5import numpy.typing as npt
6from gsfit_rs import BpProbes
7from gsfit_rs import Coils
8from gsfit_rs import Dialoop
9from gsfit_rs import FluxLoops
10from gsfit_rs import Isoflux
11from gsfit_rs import IsofluxBoundary
12from gsfit_rs import Passives
13from gsfit_rs import Plasma
14from gsfit_rs import Pressure
15from gsfit_rs import PulseSchedule
16from gsfit_rs import RogowskiCoils
17from gsfit_rs import StationaryPoint
18from gsfit_rs import Tf
19from gsfit_rs import Wall
22class DatabaseReaderProtocol(Protocol):
23 """
24 Protocol for reading experimental data.
25 Each method is responsible for initialising one of the Rust objects:
26 `bp_probes`, `coils`, `dialoop`, `flux_loops`, `isoflux`, `isoflux_boundary`, `stationary_point`, `passives`, `plasma`, `rogowski_coils`, `tf`, and `wall`.
28 The Protocol defines the inputs and outputs of each method.
29 New database readers should be implemented **all** methods.
31 The one exception is `setup_pulse_schedule`, which initialises the optional `pulse_schedule`.
32 It has a default here, returning `None`, so a reader for a machine with no gaps to measure need not implement it.
33 """
35 def setup_bp_probes(self, pulseNo: int, settings: dict[str, typing.Any], **kwargs: dict[str, typing.Any]) -> BpProbes:
36 """
37 This method initialises the Rust `BpProbes` class (Mirnov coils with a direction in the poloidal plane).
39 :param pulseNo: Pulse number, used to read from the database
40 :param settings: Dictionary containing the JSON settings read from the `settings` directory
41 :param kwargs: Additional objects, such as FreeGNSKE object
43 Initialising requires reading data from two locations:
44 1. `sensor_weights_bp_probe.json`: Which contains "fitting parameters", e.g. if a probe should be used in the fitting
45 2. Database reading (e.g. MDSplus, or FreeGNSKE object): Which contains the measurements and probe geometry
47 Different machines will use different data stores for the probe geometry and measured signals.
48 This Protocol allows different database readers to be selected.
49 The output of this method must always be a `BpProbes` object.
51 At a minimum this method should look like this:
52 ```python
53 # Initialise the BpProbes Rust class
54 bp_probes = BpProbes()
56 # Add all of the BP probes
57 for i_bp_probe in range(n_bp_probes):
58 bp_probes.add_sensor(
59 name=..., # need to be same in database and `sensor_weights_bp_probe.json` file
60 geometry_angle_pol=..., # read from a database
61 geometry_r=..., # read from a database
62 geometry_z=..., # read from a database
63 fit_settings_comment=..., # read from `sensor_weights_bp_probe.json` file
64 fit_settings_expected_value=..., # read from `sensor_weights_bp_probe.json` file
65 fit_settings_include=..., # read from `sensor_weights_bp_probe.json` file
66 fit_settings_weight=..., # read from `sensor_weights_bp_probe.json` file
67 time=..., # read from a database
68 measured=..., # read from a database
69 )
71 return bp_probes
72 ```
73 """
74 ...
76 def setup_coils(self, pulseNo: int, settings: dict[str, typing.Any], **kwargs: dict[str, typing.Any]) -> Coils:
77 """
78 This method initialises the Rust `Coils` class, which contains both the poloidal fied (PF) and toroidal field (TF) coils.
80 :param pulseNo: Pulse number, used to read from the database
81 :param settings: Dictionary containing the JSON settings read from the `settings` directory
82 :param kwargs: Additional objects, such as FreeGSNKE object
84 Initialising requires reading data from:
85 1. Database reading (e.g. MDSplus, or FreeGSNKE object): Which contains the coil geometry and current measurements
87 Different machines will use different data stores for the coil locations and currents.
88 This Protocol allows different database readers to be selected.
89 The output of this method must always be a `Coils` object.
91 At a minimum this method should look like this:
92 ```python
93 # Initialise the Coils Rust class
94 coils = Coils()
96 # Add all of the PF coils
97 for i_pf_coil in range(n_pf_coils):
98 coils.add_pf_coil(
99 coil_name=..., # read from a database
100 coil_r=..., # read from a database
101 coil_z=..., # read from a database
102 coil_d_r=..., # read from a database
103 coil_d_z=..., # read from a database
104 time=..., # read from a database
105 measured=..., # read from a database
106 )
108 return coils
109 ```
110 """
111 ...
113 def setup_dialoop(self, pulseNo: int, settings: dict[str, typing.Any], **kwargs: dict[str, typing.Any]) -> Dialoop:
114 """
115 This method initialises the Rust `Dialoop` class (the plasma's diamagnetic flux, with the vaccuum toroidal field subtracted).
117 :param pulseNo: Pulse number, used to read from the database
118 :param settings: Dictionary containing the JSON settings read from the `settings` directory
119 :param kwargs: Additional objects, such as FreeGNSKE object
121 Initialising requires reading data from two locations:
122 1. `sensor_weights_dialoop.json`: Which contains "fitting parameters", e.g. if a probe should be used in the fitting
123 2. Database reading (e.g. MDSplus, or FreeGNSKE object): Which contains the measurements and the probe geometry
125 Different machines will use different data stores for the measured signals.
126 This Protocol allows different database readers to be selected.
127 The output of this method must always be a `Dialoop` object.
129 At a minimum this method should look like this:
130 ```python
131 # Initialise the Dialoop Rust class
132 dialoop = Dialoop()
134 # Add all of the diamagnetic loops
135 for i_dialoop in range(n_dialoops):
136 dialoop.add_sensor(
137 name=..., # read from a database
138 fit_settings_comment=..., # read from `sensor_weights_dialoop.json` file
139 fit_settings_expected_value=..., # read from `sensor_weights_dialoop.json` file
140 fit_settings_include=..., # read from `sensor_weights_dialoop.json` file
141 fit_settings_weight=..., # read from `sensor_weights_dialoop.json` file
142 time=..., # read from a database
143 measured=..., # read from a database
144 )
146 return dialoop
147 ```
148 """
149 ...
151 def setup_flux_loops(self, pulseNo: int, settings: dict[str, typing.Any], **kwargs: dict[str, typing.Any]) -> FluxLoops:
152 """
153 This method initialises the Rust `FluxLoops` class (loops going in the toroidal direction measuring poloidal flux).
155 :param pulseNo: Pulse number, used to read from the database
156 :param settings: Dictionary containing the JSON settings read from the `settings` directory
157 :param kwargs: Additional objects, such as FreeGNSKE object
159 Initialising requires reading data from two locations:
160 1. `sensor_weights_flux_loops.json`: Which contains "fitting parameters", e.g. if a probe should be used in the fitting
161 2. Database reading (e.g. MDSplus, or FreeGNSKE object): Which contains the measurements and the probe geometry
163 Different machines will use different data stores for the probes geometry and measured signals.
164 This Protocol allows different database readers to be selected.
165 The output of this method must always be a `FluxLoops` object.
167 At a minimum this method should look like this:
168 ```python
169 # Initialise the FluxLoops Rust class
170 flux_loops = FluxLoops()
172 # Add all of the BP probes
173 for i_flux_loop in range(n_flux_loops):
174 flux_loops.add_sensor(
175 name=..., # need to be same in database and `sensor_weights_flux_loops.json` file
176 geometry_r=..., # read from a database
177 geometry_z=..., # read from a database
178 fit_settings_comment=..., # read from `sensor_weights_flux_loops.json` file
179 fit_settings_expected_value=..., # read from `sensor_weights_flux_loops.json` file
180 fit_settings_include=..., # read from `sensor_weights_flux_loops.json` file
181 fit_settings_weight=..., # read from `sensor_weights_flux_loops.json` file
182 time=..., # read from a database
183 measured=..., # read from a database
184 )
186 return flux_loops
187 ```
188 """
189 ...
191 def setup_isoflux_sensors(
192 self, pulseNo: int, settings: dict[str, typing.Any], times_to_reconstruct: npt.NDArray[np.float64], **kwargs: dict[str, typing.Any]
193 ) -> Isoflux:
194 """
195 This method initialises the Rust `Isoflux` class (two locations which have equal poloidal flux).
197 :param pulseNo: Pulse number, used to read from the database
198 :param settings: Dictionary containing the JSON settings read from the `settings` directory
199 :param times_to_reconstruct: Times to reconstruct the equilibrium
200 :param kwargs: Additional objects, such as FreeGNSKE object
202 :param pulseNo: Pulse number, used to read from the database
203 :param settings: Dictionary containing the JSON settings read from the `settings` directory
205 Initialising requires reading data from two locations:
206 1. `sensor_weights_isoflux.json`: Which contains "fitting parameters", e.g. if the constraint should be used in the fitting
207 2. Database reading (e.g. MDSplus, or FreeGNSKE object): Which contains the coordinates where the isoflux constraint is applied
209 Different machines will use different data stores for the isoflux coordiantes.
210 This Protocol allows different database readers to be selected.
211 The output of this method must always be a `Isoflux` object.
213 At a minimum this method should look like this:
214 ```python
215 # Initialise the Isoflux Rust class
216 isoflux = Isoflux()
218 # Add all of the isoflux constraints
219 for i_isoflux_constraint in range(n_isoflux_constraints):
220 isoflux.add_sensor(
221 name=..., # need to be same in database and `sensor_weights_isoflux.json` file
222 fit_settings_comment=..., # read from `sensor_weights_isoflux.json` file
223 fit_settings_include=..., # read from `sensor_weights_isoflux.json` file
224 fit_settings_weight=..., # read from `sensor_weights_isoflux.json` file
225 time=..., # read from a database
226 location_1_r=..., # read from a database
227 location_1_z=..., # read from a database
228 location_2_r=..., # read from a database
229 location_2_z=..., # read from a database
230 times_to_reconstruct=..., # read from `GSFIT_code_settings.json` file
231 )
233 return isoflux
234 ```
235 """
236 ...
238 def setup_isoflux_boundary_sensors(self, pulseNo: int, settings: dict[str, typing.Any], **kwargs: dict[str, typing.Any]) -> IsofluxBoundary:
239 """
240 This method initialises the Rust `IsofluxBoundary` class (a location which has the same poloidal flux as the plasma boundary).
242 :param pulseNo: Pulse number, used to read from the database
243 :param settings: Dictionary containing the JSON settings read from the `settings` directory
244 :param kwargs: Additional objects, such as FreeGNSKE object
246 Initialising requires reading data from two locations:
247 1. `sensor_weights_isoflux_boundary.json`: Which contains "fitting parameters", e.g. if the constraint should be used in the fitting
248 2. Database reading (e.g. MDSplus, or FreeGNSKE object): Which contains the coordinates where the isoflux boundary constraint is applied
250 Different machines will use different data stores for the isoflux coordiantes.
251 This Protocol allows different database readers to be selected.
252 The output of this method must always be a `IsofluxBoundary` object.
254 At a minimum this method should look like this:
255 ```python
256 # Initialise the IsofluxBoundary Rust class
257 isoflux_boundary = IsofluxBoundary()
259 # Add all of the isoflux_boundary constraints
260 for i_isoflux_boundary_constraint in range(n_isoflux_boundary_constraints):
261 isoflux_boundary.add_sensor(
262 name=..., # need to be same in database and `sensor_weights_isoflux_boundary.json` file
263 fit_settings_comment=..., # read from `sensor_weights_isoflux_boundary.json` file
264 fit_settings_include=..., # read from `sensor_weights_isoflux_boundary.json` file
265 fit_settings_weight=..., # read from `sensor_weights_isoflux_boundary.json` file
266 time=..., # read from a database
267 location_1_r=..., # read from a database
268 location_1_z=..., # read from a database
269 location_2_r=..., # read from a database
270 location_2_z=..., # read from a database
271 times_to_reconstruct=..., # read from `GSFIT_code_settings.json` file
272 )
274 return isoflux_boundary
275 ```
276 """
277 ...
279 def setup_stationary_point_sensors(self, pulseNo: int, settings: dict[str, typing.Any], **kwargs: dict[str, typing.Any]) -> StationaryPoint:
280 """
281 This method initialises the Rust `StationaryPoint` class (the magnetic axis, and x-point positions).
283 :param pulseNo: Pulse number, used to read from the database
284 :param settings: Dictionary containing the JSON settings read from the `settings` directory
285 :param kwargs: Additional objects, such as FreeGNSKE object
287 Initialising requires reading data from two locations:
288 1. `sensor_weights_magnetic_axis.json`: Which contains "fitting parameters", e.g. if a probe should be used in the fitting
289 2. Database reading (e.g. MDSplus, or FreeGNSKE object): Which contains the measurements and the probe geometry
291 Different machines will use different data stores for the measured signals.
292 This Protocol allows different database readers to be selected.
293 The output of this method must always be a `StationaryPoint` object.
295 At a minimum this method should look like this:
296 ```python
297 # Initialise the StationaryPoint Rust class
298 stationary_point = StationaryPoint()
300 # Add all of the magnetic axis sensors
301 for i_magnetic_axis in range(n_magnetic_axis_sensors):
302 stationary_point.add_sensor(
303 name=..., # read from a database
304 fit_settings_comment=..., # read from `sensor_weights_magnetic_axis.json` file
305 fit_settings_expected_value=..., # read from `sensor_weights_magnetic_axis.json` file
306 fit_settings_include=..., # read from `sensor_weights_magnetic_axis.json` file
307 fit_settings_weight=..., # read from `sensor_weights_magnetic_axis.json` file
308 time=..., # read from a database
309 mag_axis_r=..., # read from a database
310 mag_axis_z=..., # read from a database
311 times_to_reconstruct=..., # read from `GSFIT_code_settings.json` file
312 )
314 return stationary_point
315 ```
316 """
317 ...
319 def setup_passives(self, pulseNo: int, settings: dict[str, typing.Any], **kwargs: dict[str, typing.Any]) -> Passives:
320 """
321 This method initialises the Rust `Passives` class (toroidal conductors).
323 :param pulseNo: Pulse number, used to read from the database
324 :param settings: Dictionary containing the JSON settings read from the `settings` directory
325 :param kwargs: Additional objects, such as FreeGNSKE object
327 Initialising requires reading data from two locations:
328 1. `passive_dof_regularisation.json`: Which specifies how the conductor should be represented (e.g. constant current density, or eigenmode decomposition)
329 2. Database reading (e.g. MDSplus, or FreeGNSKE object): Which contains the coordinates where the passive conductors are located
331 Different machines will use different data stores for the passive conductor coordiantes.
332 This Protocol allows different database readers to be selected.
333 The output of this method must always be a `Passives` object.
335 At a minimum this method should look like this:
336 ```python
337 # Initialise the Passives Rust class
338 passives = Passives()
340 # Add all of the passive toroidal conductors
341 for i_passives in range(n_passives):
342 passives.add_passive(
343 name=..., # need to be same in database and `passive_dof_regularisation.json` file
344 r=..., # read from a database
345 z=..., # read from a database
346 d_r=..., # read from a database
347 d_z=..., # read from a database
348 angle_1=..., # read from a database
349 angle_2=..., # read from a database
350 resistivity=..., # read from a database
351 current_distribution_type=..., # read from `passive_dof_regularisation.json` file
352 n_dof=..., # read from `passive_dof_regularisation.json` file
353 regularisations=..., # read from `passive_dof_regularisation.json` file
354 regularisations_weight=..., # read from `passive_dof_regularisation.json` file
355 )
357 return passives
358 ```
359 """
360 ...
362 def setup_plasma(
363 self, pulseNo: int, settings: dict[str, typing.Any], times_to_reconstruct: npt.NDArray[np.float64], **kwargs: dict[str, typing.Any]
364 ) -> Plasma:
365 """
366 This method initialises the Rust `Plasma` class.
368 :param pulseNo: Pulse number, used to read from the database
369 :param settings: Dictionary containing the JSON settings read from the `settings` directory
370 :param kwargs: Additional objects, such as FreeGNSKE object
372 Initialising requires reading data from three locations:
373 1. `GSFIT_code_settings.json`: Which contains the plasma grid size and the numerical settings the solve is run with
374 2. `source_function_p_prime.json`: Which contains the number of degrees of freedom for p_prime, and regularisation
375 3. `source_function_ff_prime.json`: Which contains the number of degrees of freedom for ff_prime, and regularisation
377 Normally, this method will be the same for all machines.
379 The output of this method must always be a `Plasma` object.
381 At a minimum this method should look like this:
382 ```python
383 # Initialise the Plasma Rust class
384 p_prime_source_function = gsfit_rs.EfitPolynomial(
385 n_dof=..., # read from `source_function_p_prime.json` file
386 regularisations=..., # read from `source_function_p_prime.json` file
387 )
388 ff_prime_source_function = gsfit_rs.EfitPolynomial(
389 n_dof=..., # read from `source_function_ff_prime.json` file
390 regularisations=..., # read from `source_function_ff_prime.json` file
391 )
393 # Initialise the Plasma Rust class
394 plasma = Plasma(
395 n_r=..., # read from `GSFIT_code_settings.json` file
396 n_z=..., # read from `GSFIT_code_settings.json` file
397 r_min=..., # read from `GSFIT_code_settings.json` file
398 r_max=..., # read from `GSFIT_code_settings.json` file
399 z_min=..., # read from `GSFIT_code_settings.json` file
400 z_max=..., # read from `GSFIT_code_settings.json` file
401 psi_n=..., # read from `GSFIT_code_settings.json` file
402 p_prime_source_function=p_prime_source_function, # built above
403 ff_prime_source_function=ff_prime_source_function, # built above
404 initial_guess_ip=..., # read from `GSFIT_code_settings.json` file
405 initial_guess_cur_r=..., # read from `GSFIT_code_settings.json` file
406 initial_guess_cur_z=..., # read from `GSFIT_code_settings.json` file
407 initial_guess_minor_radius=..., # read from `GSFIT_code_settings.json` file
408 initial_guess_elongation=..., # read from `GSFIT_code_settings.json` file
409 n_iter_max=..., # read from `GSFIT_code_settings.json` file
410 n_iter_min=..., # read from `GSFIT_code_settings.json` file
411 n_iter_no_vertical_feedback=..., # read from `GSFIT_code_settings.json` file
412 grad_shafranov_deviation_tolerance=..., # read from `GSFIT_code_settings.json` file
413 use_anderson_mixing=..., # read from `GSFIT_code_settings.json` file
414 anderson_mixing_from_previous_iter=..., # read from `GSFIT_code_settings.json` file
415 times_to_reconstruct=times_to_reconstruct, # passed in, from `setup_timeslices`
416 )
418 return plasma
419 ```
420 """
421 ...
423 def setup_tf(self, pulseNo: int, settings: dict[str, typing.Any], **kwargs: dict[str, typing.Any]) -> Tf:
424 """
425 This method initialises the Rust `Tf` class, which holds an IMAS `tf` IDS.
427 :param pulseNo: Pulse number, used to read from the database
428 :param settings: Dictionary containing the JSON settings read from the `settings` directory
429 :param kwargs: Additional objects, such as FreeGNSKE object
431 Initialising requires reading data from:
432 1. Database reading (e.g. MDSplus, or FreeGSNKE object): Which contains the toroidal field measurement
434 Two nodes are filled. `tf/r0` is the machine's reference major radius, which the solver
435 copies onto `equilibrium/vacuum_toroidal_field/r0` so that the two IDSs cannot disagree.
436 `tf/b_field_phi_vacuum_r` is the vacuum field times major radius, which is the vacuum
437 poloidal-current function `f_vac = R0 * B_phi0 = mu_0 * i_rod / (2 * pi)`. The rod current
438 is not stored separately: the solver recovers it as `i_rod = 2 * pi * f_vac / mu_0`.
440 **Store the experimental signal**, on its own timebase, not one interpolated onto the
441 reconstruction times. `solve_grad_shafranov` interpolates it itself.
443 **`b_field_phi_vacuum_r` is signed**: positive means counter-clockwise viewed from above.
444 A reader which passes a magnitude will silently reverse the toroidal field, and with it
445 `f`, `q`, `profiles_2d/b_field_phi` and the diamagnetic flux constraint.
447 Different machines will use different data stores for the toroidal field.
448 This Protocol allows different database readers to be selected.
449 The output of this method must always be a `Tf` object.
451 At a minimum this method should look like this:
452 ```python
453 # Initialise the Tf Rust class
454 tf = Tf()
456 tf.set_r0(...) # read from `GSFIT_code_settings.json` file
458 # A machine holding a rod current converts it; one holding `R * B_phi` passes it straight
459 tf.set_b_field_phi_vacuum_r(
460 time=..., # read from a database
461 data=..., # read from a database
462 )
464 return tf
465 ```
466 """
467 ...
469 def setup_wall(self, pulseNo: int, settings: dict[str, typing.Any], **kwargs: dict[str, typing.Any]) -> Wall:
470 """
471 This method initialises the Rust `Wall` class, which holds an IMAS `wall` IDS.
473 :param pulseNo: Pulse number, used to read from the database
474 :param settings: Dictionary containing the JSON settings read from the `settings` directory
475 :param kwargs: Additional objects, such as FreeGNSKE object
477 Initialising requires reading data from:
478 1. Database reading (e.g. MDSplus, or FreeGSNKE object): Which contains the limiter geometry
480 Only `wall/description_2d(0)/limiter` is filled. Each limiter unit is a plasma facing
481 component, and every point of every unit is a candidate limit point.
483 **The order the units are added in is part of the contract**: `unit(0)` is the vacuum
484 vessel contour, and the solver uses that one, and only that one, as the region the
485 plasma is allowed to occupy. Later units are components which protrude inside it, such
486 as tiles.
488 Different machines will use different data stores for the limiter geometry.
489 This Protocol allows different database readers to be selected.
490 The output of this method must always be a `Wall` object.
492 At a minimum this method should look like this:
493 ```python
494 # Initialise the Wall Rust class
495 wall = Wall()
497 # The vacuum vessel contour must be added first
498 wall.add_limiter_unit(
499 name="vacuum_vessel", # a short identifier for the unit
500 r=..., # read from a database
501 z=..., # read from a database
502 )
504 # Any other plasma facing components follow
505 for i_unit in range(n_units):
506 wall.add_limiter_unit(
507 name=..., # a short identifier for the unit
508 r=..., # read from a database
509 z=..., # read from a database
510 )
512 return wall
513 ```
514 """
515 ...
517 def setup_rogowski_coils(self, pulseNo: int, settings: dict[str, typing.Any], **kwargs: dict[str, typing.Any]) -> RogowskiCoils:
518 """
519 This method initialises the Rust `RogowskiCoils` class.
521 :param pulseNo: Pulse number, used to read from the database
522 :param settings: Dictionary containing the JSON settings read from the `settings` directory
523 :param kwargs: Additional objects, such as FreeGNSKE object
525 Initialising requires reading data from two locations:
526 1. `sensor_weights_rogowski_coils.json`: Which contains "fitting parameters", e.g. if a probe should be used in the fitting
527 2. Database reading (e.g. MDSplus, or FreeGNSKE object): Which contains the measurements and the Rogowski coil path
529 Different machines will use different data stores for the probes geometry and measured signals.
530 This Protocol allows different database readers to be selected.
531 The output of this method must always be a `RogowskiCoils` object.
533 At a minimum this method should look like this:
534 ```python
535 # Initialise the RogowskiCoils Rust class
536 rogowski_coils = RogowskiCoils()
538 # Add all of the BP probes
539 for i_rogowski_coil in range(n_rogowski_coils):
540 rogowski_coils.add_sensor(
541 sensor_name=..., # need to be same in database and `sensor_weights_flux_loops.json` file
542 path_r, # read from a database
543 path_z, # read from a database
544 fit_settings_comment, # read from `sensor_weights_flux_loops.json` file
545 fit_settings_expected_value, # read from `sensor_weights_flux_loops.json` file
546 fit_settings_include, # read from `sensor_weights_flux_loops.json` file
547 fit_settings_weight, # read from `sensor_weights_flux_loops.json` file
548 time, # read from a database
549 measured, # read from a database
550 gaps_r=gaps_r, # read from a database
551 gaps_z=gaps_z, # read from a database
552 gaps_d_r=gaps_d_r, # read from a database
553 gaps_d_z=gaps_d_z, # read from a database
554 gaps_name=gaps_name, # read from a database
555 )
557 return rogowski_coils
558 ```
559 """
560 ...
562 def setup_pressure_sensors(self, pulseNo: int, settings: dict[str, typing.Any], **kwargs: dict[str, typing.Any]) -> Pressure:
563 """
564 This method initialises the Rust `Pressure` class (pressure measurements at specific locations in the poloidal plane).
566 :param pulseNo: Pulse number, used to read from the database
567 :param settings: Dictionary containing the JSON settings read from the `settings` directory
568 :param kwargs: Additional objects, such as FreeGNSKE object
570 Initialising requires reading data from two locations:
571 1. `sensor_weights_pressure.json`: Which contains "fitting parameters", e.g. if a sensor should be used in the fitting
572 2. Database reading (e.g. MDSplus, or FreeGNSKE object): Which contains the measurements and sensor geometry
574 Different machines will use different data stores for the sensor geometry and measured signals.
575 This Protocol allows different database readers to be selected.
576 The output of this method must always be a `Pressure` object.
578 At a minimum this method should look like this:
579 ```python
580 # Initialise the Pressure Rust class
581 pressure = Pressure()
583 # Add all of the pressure sensors
584 for i_pressure_sensor in range(n_pressure_sensors):
585 pressure.add_sensor(
586 name=..., # need to be same in database and `sensor_weights_pressure.json` file
587 geometry_r=..., # read from a database
588 geometry_z=..., # read from a database
589 fit_settings_comment=..., # read from `sensor_weights_pressure.json` file
590 fit_settings_expected_value=..., # read from `sensor_weights_pressure.json` file
591 fit_settings_include=..., # read from `sensor_weights_pressure.json` file
592 fit_settings_weight=..., # read from `sensor_weights_pressure.json` file
593 time=..., # read from a database
594 measured=..., # read from a database
595 )
597 return pressure
598 ```
599 """
600 ...
602 def setup_pulse_schedule(self, pulseNo: int, settings: dict[str, typing.Any], **kwargs: dict[str, typing.Any]) -> PulseSchedule | None:
603 """
604 This method initialises the Rust `PulseSchedule` class, which holds an IMAS `pulse_schedule` IDS.
606 :param pulseNo: Pulse number, used to read from the database
607 :param settings: Dictionary containing the JSON settings read from the `settings` directory
608 :param kwargs: Additional objects, such as FreeGNSKE object
610 **This method is optional.**
611 Unlike every other method it has a default, which returns `None`: `solve_grad_shafranov` then runs without a pulse schedule, and the equilibrium has no gaps.
613 Initialising requires reading data from:
614 1. Database reading (e.g. MDSplus): Which contains the gap definitions
616 Only the gap definitions are filled, `pulse_schedule/position_control/gap(i)`.
617 A gap is a reference point and a direction, and its value is the distance from the reference point to the plasma boundary along that direction.
618 `solve_grad_shafranov` copies the definitions onto every time-slice's `equilibrium/time_slice(itime)/boundary/gap`, and the equilibrium post-processor calculates each value.
620 **The angle is clockwise from `grad(R)`**, in the usual plot with `R` to the right and `Z` upwards, which is the equilibrium IDS's convention.
621 So an angle of `pi / 2` points towards `-Z`.
622 A machine which stores the angle counter-clockwise has to negate it.
624 Different machines will use different data stores for the gap definitions.
625 This Protocol allows different database readers to be selected.
626 The output of this method must always be a `PulseSchedule` object, or `None`.
628 At a minimum this method should look like this:
629 ```python
630 # Initialise the PulseSchedule Rust class
631 pulse_schedule = PulseSchedule()
633 # Add all of the gaps; a machine with no gaps adds none
634 for i_gap in range(n_gaps):
635 pulse_schedule.add_gap(
636 name=..., # read from a database
637 r=..., # read from a database
638 z=..., # read from a database
639 angle=..., # read from a database, clockwise from `grad(R)`
640 )
642 return pulse_schedule
643 ```
644 """
646 return None