Coverage for  / opt / hostedtoolcache / Python / 3.11.14 / x64 / lib / python3.11 / site-packages / rattlesnake / components / abstract_sysid_data_analysis.py: 29%

86 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-27 18:22 +0000

1""" 

2Defines data analysis performed for environments that use system identification 

3 

4Abstract environment that can be used to create new environment control strategies 

5in the controller that use system identification. 

6 

7Rattlesnake Vibration Control Software 

8Copyright (C) 2021 National Technology & Engineering Solutions of Sandia, LLC 

9(NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. 

10Government retains certain rights in this software. 

11 

12This program is free software: you can redistribute it and/or modify 

13it under the terms of the GNU General Public License as published by 

14the Free Software Foundation, either version 3 of the License, or 

15(at your option) any later version. 

16 

17This program is distributed in the hope that it will be useful, 

18but WITHOUT ANY WARRANTY; without even the implied warranty of 

19MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

20GNU General Public License for more details. 

21 

22You should have received a copy of the GNU General Public License 

23along with this program. If not, see <https://www.gnu.org/licenses/>. 

24""" 

25 

26import multiprocessing as mp 

27from enum import Enum 

28 

29from .abstract_message_process import AbstractMessageProcess 

30from .abstract_sysid_environment import AbstractSysIdMetadata 

31from .utilities import VerboseMessageQueue, flush_queue 

32 

33 

34class SysIDDataAnalysisCommands(Enum): 

35 """Valid commands to send to the data analysis process of an environment using system id""" 

36 

37 INITIALIZE_PARAMETERS = 0 

38 RUN_NOISE = 1 

39 RUN_TRANSFER_FUNCTION = 2 

40 START_SHUTDOWN_AND_RUN_SYSID = 3 

41 START_SHUTDOWN = 4 

42 STOP_SYSTEM_ID = 5 

43 SHUTDOWN_ACHIEVED = 6 

44 SYSTEM_ID_COMPLETE = 7 

45 LOAD_TRANSFER_FUNCTION = 8 

46 LOAD_NOISE = 9 

47 

48 

49class AbstractSysIDAnalysisProcess(AbstractMessageProcess): 

50 """Process to perform data analysis and control calculations in an environment 

51 using system id""" 

52 

53 def __init__( 

54 self, 

55 process_name: str, 

56 command_queue: VerboseMessageQueue, 

57 data_in_queue: mp.queues.Queue, 

58 data_out_queue: mp.queues.Queue, 

59 environment_command_queue: VerboseMessageQueue, 

60 log_file_queue: mp.queues.Queue, 

61 gui_update_queue: mp.queues.Queue, 

62 environment_name: str, 

63 ): 

64 """Initialize the environment process 

65 

66 Parameters 

67 ---------- 

68 process_name : str 

69 The name of the process 

70 command_queue : VerboseMessageQueue 

71 A queue used to send commands to this process 

72 data_in_queue : mp.queues.Queue 

73 A queue receiving frames of data from the data collector 

74 data_out_queue : mp.queues.Queue 

75 A queue to put the next output or analysis results for the environment to use 

76 environment_command_queue : VerboseMessageQueue 

77 A queue used to send commands to the main environment process 

78 log_file_queue : mp.queues.Queue 

79 A queue used to send log file strings 

80 gui_update_queue : mp.queues.Queue 

81 A queue used to send updates back to the graphical user interface 

82 environment_name : str 

83 The name of the environment owning this process 

84 """ 

85 super().__init__(process_name, log_file_queue, command_queue, gui_update_queue) 

86 self.map_command( 

87 SysIDDataAnalysisCommands.INITIALIZE_PARAMETERS, 

88 self.initialize_sysid_parameters, 

89 ) 

90 self.map_command(SysIDDataAnalysisCommands.RUN_NOISE, self.run_sysid_noise) 

91 self.map_command( 

92 SysIDDataAnalysisCommands.RUN_TRANSFER_FUNCTION, 

93 self.run_sysid_transfer_function, 

94 ) 

95 self.map_command(SysIDDataAnalysisCommands.STOP_SYSTEM_ID, self.stop_sysid) 

96 self.map_command(SysIDDataAnalysisCommands.LOAD_NOISE, self.load_sysid_noise) 

97 self.map_command( 

98 SysIDDataAnalysisCommands.LOAD_TRANSFER_FUNCTION, 

99 self.load_sysid_transfer_function, 

100 ) 

101 self.environment_name = environment_name 

102 self.environment_command_queue = environment_command_queue 

103 self.data_in_queue = data_in_queue 

104 self.data_out_queue = data_out_queue 

105 self.parameters = None 

106 self.frames = None 

107 self.frequencies = None 

108 self.sysid_frf = None 

109 self.sysid_coherence = None 

110 self.sysid_response_cpsd = None 

111 self.sysid_reference_cpsd = None 

112 self.sysid_response_noise = None 

113 self.sysid_reference_noise = None 

114 self.sysid_condition = None 

115 self.startup = True 

116 

117 def initialize_sysid_parameters(self, data: AbstractSysIdMetadata): 

118 """Stores parameters describing the system identification into the object 

119 

120 Parameters 

121 ---------- 

122 data : AbstractSysIdMetadata 

123 A metadata object containing the parameters to define the system identification 

124 """ 

125 self.parameters = data 

126 

127 def load_sysid_noise(self, spectral_data): 

128 """Loads noise data from a previous system identification 

129 

130 Parameters 

131 ---------- 

132 spectral_data : tuple 

133 A tuple containing frames, frequencies, system id FRFs, coherence, response cpsd, 

134 reference_cpsd and condition number 

135 """ 

136 self.log("Obtained Spectral Data") 

137 ( 

138 self.frames, 

139 self.frequencies, 

140 _, 

141 _, 

142 self.sysid_response_noise, 

143 self.sysid_reference_noise, 

144 _, 

145 ) = spectral_data 

146 

147 def load_sysid_transfer_function(self, spectral_data, skip_sysid=True): 

148 """Loads system ID data from a previous system identification 

149 

150 Parameters 

151 ---------- 

152 spectral_data : tuple 

153 A tuple containing frames, frequencies, system id FRFs, coherence, response cpsd, 

154 reference_cpsd and condition number 

155 skip_sysid : bool, optional 

156 If True, send the system identification complete flag to the controller. By default True 

157 """ 

158 self.log("Obtained Spectral Data") 

159 ( 

160 self.frames, 

161 self.frequencies, 

162 self.sysid_frf, 

163 self.sysid_coherence, 

164 self.sysid_response_cpsd, 

165 self.sysid_reference_cpsd, 

166 self.sysid_condition, 

167 ) = spectral_data 

168 if skip_sysid: 

169 self.environment_command_queue.put( 

170 self.process_name, 

171 ( 

172 SysIDDataAnalysisCommands.SYSTEM_ID_COMPLETE, 

173 ( 

174 self.frames, 

175 0, 

176 self.frequencies, 

177 self.sysid_frf, 

178 self.sysid_coherence, 

179 self.sysid_response_cpsd, 

180 self.sysid_reference_cpsd, 

181 self.sysid_condition, 

182 self.sysid_response_noise, 

183 self.sysid_reference_noise, 

184 ), 

185 ), 

186 ) 

187 

188 def run_sysid_noise(self, auto_shutdown): 

189 """Starts and runs the system identification noise phase. 

190 

191 Parameters 

192 ---------- 

193 auto_shutdown : bool 

194 If True, the environment will automatically shut down when the requested number of 

195 frames is reached. If False, the noise characterization will run until manually 

196 stopped. 

197 """ 

198 if self.startup: 

199 self.startup = False 

200 self.frames = 0 

201 spectral_data = flush_queue(self.data_in_queue) 

202 if len(spectral_data) > 0: 

203 self.load_sysid_noise(spectral_data[-1]) 

204 self.gui_update_queue.put( 

205 ( 

206 self.environment_name, 

207 ( 

208 "noise_update", 

209 ( 

210 self.frames, 

211 self.parameters.sysid_noise_averages, 

212 self.frequencies, 

213 self.sysid_response_noise, 

214 self.sysid_reference_noise, 

215 ), 

216 ), 

217 ) 

218 ) 

219 if auto_shutdown and self.parameters.sysid_noise_averages == self.frames: 

220 self.environment_command_queue.put( 

221 self.process_name, 

222 (SysIDDataAnalysisCommands.START_SHUTDOWN_AND_RUN_SYSID, None), 

223 ) 

224 self.stop_sysid(None) 

225 else: 

226 self.command_queue.put( 

227 self.process_name, (SysIDDataAnalysisCommands.RUN_NOISE, auto_shutdown) 

228 ) 

229 

230 def run_sysid_transfer_function(self, auto_shutdown): 

231 """Starts and runs the system identification 

232 

233 Parameters 

234 ---------- 

235 auto_shutdown : bool 

236 If True, the system identification will stop automatically upon reaching the requested 

237 number of measurement frames. If False, it will run indefinitely until manually 

238 stopped. 

239 """ 

240 if self.startup: 

241 self.startup = False 

242 self.frames = 0 

243 spectral_data = flush_queue(self.data_in_queue) 

244 if len(spectral_data) > 0: 

245 self.load_sysid_transfer_function(spectral_data[-1], skip_sysid=False) 

246 self.gui_update_queue.put( 

247 ( 

248 self.environment_name, 

249 ( 

250 "sysid_update", 

251 ( 

252 self.frames, 

253 self.parameters.sysid_averages, 

254 self.frequencies, 

255 self.sysid_frf, 

256 self.sysid_coherence, 

257 self.sysid_response_cpsd, 

258 self.sysid_reference_cpsd, 

259 self.sysid_condition, 

260 ), 

261 ), 

262 ) 

263 ) 

264 if auto_shutdown and self.parameters.sysid_averages == self.frames: 

265 self.environment_command_queue.put( 

266 self.process_name, 

267 (SysIDDataAnalysisCommands.START_SHUTDOWN, (False, True)), 

268 ) 

269 self.stop_sysid(None) 

270 self.environment_command_queue.put( 

271 self.process_name, 

272 ( 

273 SysIDDataAnalysisCommands.SYSTEM_ID_COMPLETE, 

274 ( 

275 self.frames, 

276 self.parameters.sysid_averages, 

277 self.frequencies, 

278 self.sysid_frf, 

279 self.sysid_coherence, 

280 self.sysid_response_cpsd, 

281 self.sysid_reference_cpsd, 

282 self.sysid_condition, 

283 self.sysid_response_noise, 

284 self.sysid_reference_noise, 

285 ), 

286 ), 

287 ) 

288 else: 

289 self.command_queue.put( 

290 self.process_name, 

291 (SysIDDataAnalysisCommands.RUN_TRANSFER_FUNCTION, auto_shutdown), 

292 ) 

293 

294 def stop_sysid(self, data): # pylint: disable=unused-argument 

295 """Stops the currently running system identification phase 

296 

297 Parameters 

298 ---------- 

299 data : ignored 

300 This argument is not used, but is required by the calling signature of functions 

301 that get called via the command map. 

302 """ 

303 # Remove any run_transfer_function or run_control from the queue 

304 instructions = self.command_queue.flush(self.process_name) 

305 for instruction in instructions: 

306 if not instruction[0] in [ 

307 SysIDDataAnalysisCommands.RUN_NOISE, 

308 SysIDDataAnalysisCommands.RUN_TRANSFER_FUNCTION, 

309 ]: 

310 self.command_queue.put(self.process_name, instruction) 

311 flush_queue(self.data_out_queue) 

312 self.startup = True 

313 self.environment_command_queue.put( 

314 self.process_name, (SysIDDataAnalysisCommands.SHUTDOWN_ACHIEVED, None) 

315 ) 

316 

317 

318def sysid_data_analysis_process( 

319 environment_name: str, 

320 command_queue: VerboseMessageQueue, 

321 data_in_queue: mp.queues.Queue, 

322 data_out_queue: mp.queues.Queue, 

323 environment_command_queue: VerboseMessageQueue, 

324 gui_update_queue: mp.queues.Queue, 

325 log_file_queue: mp.queues.Queue, 

326 process_name=None, 

327): 

328 """An function called by multiprocessing to start up the system identification analysis 

329 process. 

330 

331 Some environments may override the AbstractSysIDAnalysisProcess class and therefore should 

332 redefine this function to call that class. 

333 

334 Parameters 

335 ---------- 

336 environment_name : str 

337 The name of the environment 

338 command_queue : VerboseMessageQueue 

339 A queue used to send commands to this process 

340 data_in_queue : mp.queues.Queue 

341 A queue used to send frames of data and spectral quantities to the data analysis process 

342 data_out_queue : mp.queues.Queue 

343 A queue used to send control and analysis results back to the environment 

344 environment_command_queue : VerboseMessageQueue 

345 A queue used to send commands to the environment 

346 gui_update_queue : mp.queues.Queue 

347 A queue used to send updates to the graphical user interface 

348 log_file_queue : mp.queues.Queue 

349 A queue used to send log file messages 

350 process_name : _type_, optional 

351 A name for the process. If not specified, it will be the environment name appended with 

352 Data Analysis. 

353 """ 

354 data_analysis_instance = AbstractSysIDAnalysisProcess( 

355 environment_name + " Data Analysis" if process_name is None else process_name, 

356 command_queue, 

357 data_in_queue, 

358 data_out_queue, 

359 environment_command_queue, 

360 log_file_queue, 

361 gui_update_queue, 

362 environment_name, 

363 ) 

364 

365 data_analysis_instance.run()