66from panel .viewable import Viewer
77
88from waveform_editor .derived_waveform import DerivedWaveform
9- from waveform_editor .tendencies .linear import LinearTendency
10- from waveform_editor .tendencies .piecewise import PiecewiseLinearTendency
11- from waveform_editor .waveform import Waveform
12-
139from waveform_editor .settings import settings
10+ from waveform_editor .tendencies .piecewise import PiecewiseLinearTendency
1411
1512
1613class CoilCurrents (Viewer ):
1714 coil_ui = param .List (
1815 doc = "List of tuples containing the checkboxes and sliders for the coil currents"
1916 )
20- export_time = param .Number (doc = "Time to export coil currents to" )
17+ export_time = param .Number (
18+ doc = "Select a time at which coil currents will be saved to waveforms"
19+ )
2120
2221 def __init__ (self , main_gui , ** params ):
2322 super ().__init__ (** params )
@@ -39,31 +38,17 @@ def __init__(self, main_gui, **params):
3938 confirm_button = pn .widgets .Button (
4039 on_click = lambda event : self ._store_coil_currents (),
4140 name = "Save Currents as Waveforms" ,
42- margin = 30 ,
41+ margin = ( 30 , 0 , 0 , 0 ) ,
4342 )
4443 self .panel = pn .Column (
45- pn .Row (export_time_input , confirm_button , no_ids_message ),
44+ pn .Row (
45+ export_time_input , confirm_button , visible = self .param .coil_ui .rx .bool ()
46+ ),
47+ no_ids_message ,
4648 guide_message ,
4749 self .sliders_ui ,
48- self .modal ,
4950 )
5051
51- def _open_modal (self ):
52- self ._coil_currents_valid ()
53- self .modal .show ()
54-
55- @param .depends ("export_time" , watch = True )
56- def _coil_currents_valid (self ):
57- self .coil_export_valid = True
58- for i in range (len (self .coil_ui )):
59- name = f"pf_active/coil({ i + 1 } )/current/data"
60- if name in self .main_gui .config .waveform_map :
61- tendencies = self .main_gui .config [name ].tendencies
62- if tendencies :
63- end_time = tendencies [- 1 ].end
64- if end_time >= self .export_time :
65- self .coil_export_valid = False
66-
6752 @param .depends ("coil_ui" , watch = True )
6853 def _update_slider_grid (self ):
6954 self .sliders_ui .objects = self .coil_ui
@@ -100,66 +85,120 @@ def create_ui(self, pf_active):
10085 self .coil_ui = new_coil_ui
10186
10287 def _store_coil_currents (self ):
88+ """Store the current values from the coil UI sliders into the waveform
89+ configuration.
90+ """
10391 coil_currents = self ._get_currents ()
10492 config = self .main_gui .config
10593 new_waveforms_created = False
94+ group_name = "Coil Currents"
10695
107- for i in range (len (self .coil_ui )):
108- name = f"pf_active/coil({ i + 1 } )/current/data"
109- if name in self .main_gui .config .waveform_map :
110- tendencies = self .main_gui .config [name ].tendencies
111- if tendencies :
112- end_time = tendencies [- 1 ].end
113- if end_time >= self .export_time :
114- pn .state .notifications .error (
115- "Export time must be later than the end of any existing waveforms"
116- )
117- return
96+ if not self ._has_valid_export_time ():
97+ return
11898
11999 for i , current in enumerate (coil_currents ):
120100 name = f"pf_active/coil({ i + 1 } )/current/data"
121- eps = 1e-100
122- # Piecewise tendencies must contain at least two points
123- new_piecewise = f"- {{type: piecewise, time: [{ self .export_time } , { self .export_time + eps } ], value: [{ current } , { current } ]}}"
124- if not name in config .waveform_map :
125- group_name = "Coil Currents"
101+ if name not in config .waveform_map :
126102 if group_name not in config .groups :
127103 config .add_group (group_name , [])
128- waveform = config .parser .parse_waveform (f"{ name } :\n { new_piecewise } " )
129- config .add_waveform (waveform , [group_name ])
104+ self ._create_new_waveform (config , name , current , group_name )
130105 new_waveforms_created = True
131106 else :
132107 waveform = config [name ]
133108 if isinstance (waveform , DerivedWaveform ):
134- pn .state .error (
135- f"Could not store coil current in { name } , because it is a derived waveform"
109+ pn .state .notifications .error (
110+ f"Could not store coil current in waveform { name !r} , "
111+ "because it is a derived waveform"
136112 )
137113 continue
138-
139- last_tendency = waveform .tendencies [- 1 ]
140- if isinstance (last_tendency , PiecewiseLinearTendency ):
141- waveform .yaml [- 1 ]["time" ].append (float (self .export_time ))
142- waveform .yaml [- 1 ]["value" ].append (float (current ))
143- yaml_str = f"{ name } :\n { waveform .get_yaml_string ()} "
144- else :
145- end = waveform .tendencies [- 1 ].end
146- append_new_piecewise = f"- {{type: piecewise, time: [{ end } , { self .export_time } ], value: [{ current } , { current } ]}}"
147- yaml_str = (
148- f"{ name } :\n { waveform .get_yaml_string ()} { append_new_piecewise } "
149- )
150- new_waveform = config .parse_waveform (yaml_str )
151- config .replace_waveform (new_waveform )
114+ self ._append_to_existing_waveform (config , name , current )
152115
153116 if new_waveforms_created :
154117 self .main_gui .selector .refresh ()
155118 pn .state .notifications .warning (
156- f"Could not find an existing waveform to store the coil current. A new waveform is created in the { group_name !r} group"
119+ "Could not find an existing waveform to store the coil current. "
120+ f"New waveform(s) are created in the { group_name !r} group"
157121 )
158122 else :
159123 pn .state .notifications .success (
160- "The coil currents were appended to their respective waveforms."
124+ "The values of the coil currents were appended to their respective "
125+ "waveforms."
161126 )
162127
128+ def _append_to_existing_waveform (self , config , name , current ):
129+ """Append coil current value to an existing waveform. If the last tendency is a
130+ piecewise tendency, it is extended, otherwise a new piecewise tendency
131+ is added.
132+
133+ Args:
134+ config: The waveform configuration.
135+ name: Name of the waveform.
136+ current: Coil current value to append.
137+ """
138+ waveform = config [name ]
139+ last_tendency = waveform .tendencies [- 1 ]
140+
141+ # Either append to existing piecewise linear tendency, or create new
142+ # piecewise linear tendency
143+ if isinstance (last_tendency , PiecewiseLinearTendency ):
144+ waveform .yaml [- 1 ]["time" ].append (float (self .export_time ))
145+ waveform .yaml [- 1 ]["value" ].append (float (current ))
146+ yaml_str = f"{ name } :\n { waveform .get_yaml_string ()} "
147+ else :
148+ end = waveform .tendencies [- 1 ].end
149+ new_piecewise = (
150+ f"- {{type: piecewise, time: [{ end } , { self .export_time } ], "
151+ f"value: [{ current } , { current } ]}}"
152+ )
153+ yaml_str = f"{ name } :\n { waveform .get_yaml_string ()} { new_piecewise } "
154+
155+ new_waveform = config .parse_waveform (yaml_str )
156+ config .replace_waveform (new_waveform )
157+
158+ def _create_new_waveform (self , config , name , current , group_name ):
159+ """Create a new waveform for a coil current when none exists.
160+
161+ Args:
162+ config: The waveform configuration.
163+ name: Name of the waveform.
164+ current: Coil current value to append.
165+ group_name: Name of the group to place the new waveform in.
166+ """
167+ # Piecewise tendencies must contain at least two points
168+ eps = 1e-9
169+ new_piecewise = (
170+ f"- {{type: piecewise, time: [{ self .export_time - eps } , "
171+ f"{ self .export_time } ], value: [{ current } , { current } ]}}"
172+ )
173+ waveform = config .parser .parse_waveform (f"{ name } :\n { new_piecewise } " )
174+ config .add_waveform (waveform , [group_name ])
175+
176+ def _has_valid_export_time (self ):
177+ """Check whether the export time is later than the last tendency endpoint
178+ in all existing coil current waveforms.
179+
180+ Returns:
181+ True if export time is valid, False otherwise.
182+ """
183+ latest_time = None
184+ for i in range (len (self .coil_ui )):
185+ name = f"pf_active/coil({ i + 1 } )/current/data"
186+ if name in self .main_gui .config .waveform_map :
187+ tendencies = self .main_gui .config [name ].tendencies
188+ if tendencies :
189+ end_time = tendencies [- 1 ].end
190+ if latest_time is None or end_time > latest_time :
191+ latest_time = end_time
192+
193+ if latest_time is not None and latest_time >= self .export_time :
194+ pn .state .notifications .error (
195+ f"Invalid export time: { self .export_time } . It must be greater than the "
196+ f"last endpoint of existing coil current waveforms ({ latest_time } )."
197+ )
198+ return False
199+
200+ return True
201+
163202 def fill_pf_active (self , pf_active ):
164203 """Update the coil currents of the provided pf_active IDS. Only coils with
165204 their corresponding checkbox checked are updated.
@@ -172,11 +211,11 @@ def fill_pf_active(self, pf_active):
172211 pf_active .coil [i ].current .data = np .array ([slider .value ])
173212
174213 def _get_currents (self ):
214+ """Returns the coil current values in a list."""
175215 coil_currents = []
176216 for coil_ui in self .coil_ui :
177217 _ , slider = coil_ui .objects
178218 coil_currents .append (slider .value )
179-
180219 return coil_currents
181220
182221 def sync_ui_with_pf_active (self , pf_active ):
0 commit comments