1# -*- coding: utf-8 -*-
2"""
3This is an abstract interface to that defines an interactive control law in
4Rattlesnake that can have it's own graphical user interface and interactive
5elements in the control law. This control law will need to interface with the
6environment in order to send parameters and receive data. It will also need to
7handle building up a graphical user interface.
8
9The general workflow for this function should be:
10 1. The control law is selected in the environment user interface with a new
11 type associated with it: "Interactive".
12 2. The control law must define four classes.
13 a. The UI class will handle specification of parameters
14 and/or visualization of results. Minor calculations may also be
15 performed in the UI class, accepting that the UI will lock up while the
16 calculations are performed unless sent to a separate process.
17 b. The control calculation class will handle the major calculations
18 involved with computing the next output dataset.
19 c. The control law must define a "parameters" class that contains
20 parameters that the control class will need. These can be things
21 like regularization parameters, weighting parameters, transformation
22 matrices; whatever the control law needs to perform its calculation.
23 d. The control law must define a "results" class that contains
24 information that needs to be passed from the control back to the
25 user interface.
26 2. The control law will need functions to handle the passing of the data
27 objects between UI and calculation class. These should be
28 `send_parameters` and `update_ui` functions for the UI and
29 `update_parameters` and `send_results` functions for the calculation.
30 The environment will handle calling these functions.
31 3. Communication between the UI and the calculation will occur via channels
32 set up in the environment. The data analysis queue will be used to send
33 information to the control law, and the GUI update queue will be used to
34 send information back to the UI.
35 4. When the Initialize Environment button is clicked, the control law must
36 initialize itself and build a graphical user interface for the control
37 law. Extra parameters from the Rattlesnake box will be sent to this
38 initialization function, and the GUI builder can use this for initial
39 values for the control law, or for whatever other reason there might be.
40 5. When the "Start" button is clicked on the System Identification, the
41 control law UI will send it's current parameter state to the calculation
42 class. The calculation class will update itself.
43 6. When the system identification completes, the control calculation will
44 receive the system identification information and then perform a control
45 calculation for the prediction step. After this control calculation,
46 results will be obtained and sent back to the UI.
47 7. During control, the control law will repeatedly send updates back to the
48 UI. The UI will not automatically send parameters unless told to do so
49 through a callback in its UI.
50
51
52Rattlesnake Vibration Control Software
53Copyright (C) 2021 National Technology & Engineering Solutions of Sandia, LLC
54(NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
55Government retains certain rights in this software.
56
57This program is free software: you can redistribute it and/or modify
58it under the terms of the GNU General Public License as published by
59the Free Software Foundation, either version 3 of the License, or
60(at your option) any later version.
61
62This program is distributed in the hope that it will be useful,
63but WITHOUT ANY WARRANTY; without even the implied warranty of
64MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
65GNU General Public License for more details.
66
67You should have received a copy of the GNU General Public License
68along with this program. If not, see <https://www.gnu.org/licenses/>.
69"""
70
71from abc import ABC, abstractmethod
72
73from .utilities import GlobalCommands
74
75
76class AbstractControlLawUI(ABC):
77 """A user interface to allow users to create interactive control laws"""
78
79 @abstractmethod
80 def __init__(self, process_name, send_parameters_queue, window, parent_ui_class):
81 """Initializes an interactive control law
82
83 Parameters
84 ----------
85 process_name : str
86 The process name associated with this user interface
87 send_parameters_queue : np.Queue
88 A Multiprocessing queue into which parameters defined by the UI will be put to be
89 used by the environment process
90 window : QDialog
91 The dialog window that the user interface will be placed in
92 parent_ui_class : RandomVibrationUI
93 The user interface object for the environment that spawned this control law.
94 """
95 self.send_parameters_queue = send_parameters_queue
96 self.process_name = process_name
97 self.window = window
98 self.parent_ui_class = parent_ui_class
99 self.data_acquisition_parameters = None
100 self.environment_parameters = None
101
102 def initialize_parameters(self, data_acquisition_parameters, environment_parameters):
103 """Stores the data acquisition and environment parameters to the UI
104
105 Parameters
106 ----------
107 data_acquisition_parameters : DataAcquisitionParameters
108 The global data acquisition parameters like sample rate and channel table.
109 environment_parameters : RandomVibrationMetadata
110 Parameters defining the environment
111 """
112 self.data_acquisition_parameters = data_acquisition_parameters
113 self.environment_parameters = environment_parameters
114
115 def send_parameters(self):
116 """Sends parameters from the UI to the environment process"""
117 self.send_parameters_queue.put(
118 self.process_name,
119 (
120 GlobalCommands.UPDATE_INTERACTIVE_CONTROL_PARAMETERS,
121 self.collect_parameters(),
122 ),
123 )
124
125 def run_callback(self, command, *args):
126 """Tells the environment process to run a specific command"""
127 self.send_parameters_queue.put(
128 self.process_name,
129 (GlobalCommands.SEND_INTERACTIVE_COMMAND, (command, args)),
130 )
131
132 @abstractmethod
133 def collect_parameters(self) -> dict:
134 """Collects parameters from the UI to send to the environment process"""
135
136 @abstractmethod
137 def update_ui_control(self, results: dict):
138 """Updates the UI with results from the control law
139
140 Parameters
141 ----------
142 results : dict
143 A dictionary containing information the UI might need to update itself
144 """
145
146 @abstractmethod
147 def update_ui_sysid(
148 self,
149 sysid_frf, # Transfer Functions
150 sysid_response_noise, # Noise levels and correlation
151 sysid_reference_noise, # from the system identification
152 sysid_response_cpsd, # Response levels and correlation
153 sysid_reference_cpsd, # from the system identification
154 sysid_coherence, # Coherence from the system identification
155 ):
156 """Updates the UI with information from the system identification
157
158 Parameters
159 ----------
160 sysid_frf : np.ndarray
161 The system transfer functions
162 sysid_response_noise : np.ndarray
163 The noise CPSD matrix at the control channels from the system identification
164 sysid_reference_noise : np.ndarray
165 The noise CPSD matrix at the drive channels from the system identification
166 sysid_response_cpsd : np.ndarray
167 The Buzz CPSD at the control channels from the system identification
168 sysid_reference_cpsd : np.ndarray
169 The Buzz CPSD at the drive channels from the system identification
170 sysid_coherence : np.ndarray
171 The multiple coherence for each of the control channels from the system identification
172 """
173
174 def close(self):
175 """Closes the UI window"""
176 self.window.close()
177
178
179class AbstractControlLawComputation(ABC):
180 """Computation process for the interactive control law that runs on the environment
181 data analysis process"""
182
183 @abstractmethod
184 def __init__(self, environment_name, gui_update_queue):
185 """Initializes the control process
186
187 Parameters
188 ----------
189 environment_name : str
190 The name of the environment that the control law is running in
191 gui_update_queue : mp.Queue
192 The queue into which GUI updates will be put
193 """
194 self.environment_name = environment_name
195 self.gui_update_queue = gui_update_queue
196 self._command_map = {}
197
198 def send_results(self):
199 """Sends results of the control calculation to the UI to update itself"""
200 self.gui_update_queue.put(
201 (
202 self.environment_name,
203 ("interactive_control_update", self.collect_results()),
204 )
205 )
206
207 @abstractmethod
208 def update_parameters(self, parameters: dict):
209 """Updates the control computation based on parameters sent from the UI
210
211 Parameters
212 ----------
213 parameters : dict
214 A dictionary containing relevant parameters from the user interface
215 """
216
217 @abstractmethod
218 def collect_results(self) -> dict:
219 """Collects results from the computation to send to the user interface for updates
220
221 Returns
222 -------
223 dict
224 A dictionary containing relevant results to display on the user interface
225 """
226
227 @abstractmethod
228 def control(self):
229 """Executes the control calculation"""
230
231 @abstractmethod
232 def system_id_update(self):
233 """Updates the control law based on parameters received from the system identification"""
234
235 @staticmethod
236 @abstractmethod
237 def get_ui_class():
238 """Returns the User Interface class corresponding to this calculation class"""
239
240 @property
241 def command_map(self) -> dict:
242 """A dictionary that maps commands received by the ``command_queue``
243 to functions in the class"""
244 return self._command_map
245
246 def map_command(self, key, function):
247 """A function that maps an instruction to a function in the ``command_map``
248
249 Parameters
250 ----------
251 key :
252 The instruction that will be pulled from the ``command_queue``
253
254 function :
255 A reference to the function that will be called when the ``key``
256 message is received.
257
258 """
259 self._command_map[key] = function
260
261 def send_command(self, command: tuple):
262 """A function used by the environment process to execute commands sent from the UI
263 The UI object uses the `send_parameters_queue` (which is also the environment_command_queue)
264 to send custom instructions (set up using the map_command function) to this computation
265 object.
266
267 Parameters
268 ----------
269 command : tuple
270 (command enumeration, command arguments)
271
272 Returns
273 -------
274 Any
275 """
276 func, args = command
277 function = self._command_map[func]
278 return function(*args)