39 from shesha.supervisor.components import AtmosCompass, DmCompass, RtcCompass, TargetCompass, TelescopeCompass, WfsCompass, CoronagraphCompass
46 from typing
import Iterable
50 """ This class implements generic supervisor to handle compass simulation
52 Attributes inherited from GenericSupervisor:
53 context : (CarmaContext) : a CarmaContext instance
55 config : (config) : Parameters structure
57 is_init : (bool) : Flag equals to True if the supervisor has already been initialized
59 iter : (int) : Frame counter
62 telescope : (TelescopeComponent) : a TelescopeComponent instance
64 atmos : (AtmosComponent) : An AtmosComponent instance
66 target : (targetComponent) : A TargetComponent instance
68 wfs : (WfsComponent) : A WfsComponent instance
70 dms : (DmComponent) : A DmComponent instance
72 rtc : (RtcComponent) : A Rtc component instance
74 cacao : (bool) : CACAO features enabled in the RTC
76 basis : (ModalBasis) : a ModalBasis instance (optimizer)
78 calibration : (Calibration) : a Calibration instance (optimizer)
80 modalgains : (ModalGains) : a ModalGain instance (optimizer) using CLOSE algorithm
82 close_modal_gains : (list of floats) : list of the previous values of the modal gains
85 def __init__(self, config, *, cacao: bool =
False):
86 """ Instantiates a CompassSupervisor object
89 config: (config module) : Configuration module
92 cacao : (bool) : If True, enables CACAO features in RTC (Default is False)
93 Requires OCTOPUS to be installed
95 self.
cacaocacao = cacao
97 self.
atmosatmos =
None
104 GenericSupervisor.__init__(self, config)
108 if config.p_controllers
is not None:
118 """Initialize the telescope component of the supervisor as a TelescopeCompass
122 def _init_atmos(self):
123 """Initialize the atmosphere component of the supervisor as a AtmosCompass
128 """Initialize the DM component of the supervisor as a DmCompass
132 def _init_target(self):
133 """Initialize the target component of the supervisor as a TargetCompass
135 if self.
teltel
is not None:
138 raise ValueError(
"Configuration not loaded or Telescope not initilaized")
141 """Initialize the wfs component of the supervisor as a WfsCompass
143 if self.
teltel
is not None:
146 raise ValueError(
"Configuration not loaded or Telescope not initilaized")
149 """Initialize the rtc component of the supervisor as a RtcCompass
151 if self.
wfswfs
is not None:
155 raise ValueError(
"Configuration not loaded or Telescope not initilaized")
157 def _init_components(self) -> None:
158 """ Initialize all the components
161 if self.
configconfig.p_tel
is None or self.
configconfig.p_geom
is None:
162 raise ValueError(
"Telescope geometry must be defined (p_geom and p_tel)")
165 if self.
configconfig.p_atmos
is not None:
167 if self.
configconfig.p_dms
is not None:
169 if self.
configconfig.p_targets
is not None:
171 if self.
configconfig.p_wfss
is not None:
173 if self.
configconfig.p_controllers
is not None or self.
configconfig.p_centroiders
is not None:
175 if self.
configconfig.p_coronos
is not None:
178 GenericSupervisor._init_components(self)
180 def _init_coronagraph(self):
181 """ Initialize the coronagraph
183 self.
coronocorono = CoronagraphCompass()
184 for p_corono
in self.
configconfig.p_coronos:
187 def next(self, *, move_atmos: bool =
True, nControl: int = 0,
188 tar_trace: Iterable[int] =
None, wfs_trace: Iterable[int] =
None,
189 do_control: bool =
True, apply_control: bool =
True,
190 compute_tar_psf: bool =
True, compute_corono: bool=
True) ->
None:
191 """Iterates the AO loop, with optional parameters.
193 Overload the GenericSupervisor next() method to handle the GEO controller
194 specific raytrace order operations
197 move_atmos: (bool): move the atmosphere for this iteration. Default is True
199 nControl: (int): Controller number to use. Default is 0 (single control configuration)
201 tar_trace: (List): list of targets to trace. None is equivalent to all (default)
203 wfs_trace: (List): list of WFS to trace. None is equivalent to all (default)
205 do_control : (bool) : Performs RTC operations if True (Default)
207 apply_control: (bool): if True (default), apply control on DMs
209 compute_tar_psf : (bool) : If True (default), computes the PSF at the end of the iteration
211 compute_corono: (bool): If True (default), computes the coronagraphic image
217 nControl = [nControl]
220 geo_index =
next(( i
for i,c
in enumerate(self.
configconfig.p_controllers)
221 if c.type== scons.ControllerType.GEO ), -1)
223 if tar_trace
is None and self.
targettarget
is not None:
224 tar_trace = range(len(self.
configconfig.p_targets))
225 if wfs_trace
is None and self.
wfswfs
is not None:
226 wfs_trace = range(len(self.
configconfig.p_wfss))
228 if move_atmos
and self.
atmosatmos
is not None:
229 self.
atmosatmos.move_atmos()
231 self.
teltel.update_input_phase()
232 if ( geo_index > -1):
234 if tar_trace
is not None:
236 if self.
atmosatmos.is_enable:
237 self.
targettarget.raytrace(t, tel=self.
teltel, atm=self.
atmosatmos, ncpa=
False)
239 self.
targettarget.raytrace(t, tel=self.
teltel, ncpa=
False)
241 if do_control
and self.
rtcrtc
is not None:
242 self.
rtcrtc.do_control(nControl, sources=self.
targettarget.sources)
243 self.
targettarget.raytrace(t, dms=self.
dmsdms, ncpa=
True, reset=
False)
245 self.
rtcrtc.apply_control(nControl)
247 self.
rtcrtc.publish()
249 if tar_trace
is not None:
251 if self.
atmosatmos.is_enable:
252 self.
targettarget.raytrace(t, tel=self.
teltel, atm=self.
atmosatmos,
255 self.
targettarget.raytrace(t, tel=self.
teltel, dms=self.
dmsdms)
257 if wfs_trace
is not None:
259 if self.
atmosatmos.is_enable:
260 self.
wfswfs.raytrace(w, tel=self.
teltel, atm=self.
atmosatmos)
262 self.
wfswfs.raytrace(w, tel=self.
teltel)
264 if not self.
configconfig.p_wfss[w].open_loop
and self.
dmsdms
is not None:
265 self.
wfswfs.raytrace(w, dms=self.
dmsdms, ncpa=
False, reset=
False)
266 self.
wfswfs.compute_wfs_image(w)
267 if do_control
and self.
rtcrtc
is not None:
268 for ncontrol
in nControl :
269 self.
rtcrtc.do_centroids(ncontrol)
270 self.
rtcrtc.do_control(ncontrol)
271 self.
rtcrtc.do_clipping(ncontrol)
274 for ncontrol
in nControl :
275 self.
rtcrtc.apply_control(ncontrol)
278 self.
rtcrtc.publish()
281 for tar_index
in tar_trace:
282 self.
targettarget.comp_tar_image(tar_index)
283 self.
targettarget.comp_strehl(tar_index)
285 if self.
coronocorono
is not None and compute_corono:
286 for coro_index
in range(len(self.
configconfig.p_coronos)):
287 self.
coronocorono.compute_image(coro_index)
289 if self.
configconfig.p_controllers[0].close_opti
and (
not self.
rtcrtc._rtc.d_control[0].open_loop):
295 def _print_strehl(self, monitoring_freq: int, iters_time: float, total_iters: int, *,
297 """ Print the Strehl ratio SE and LE from a target on the terminal, the estimated remaining time and framerate
300 monitoring_freq : (int) : Number of frames between two prints
302 iters_time : (float) : time elapsed between two prints
304 total_iters : (int) : Total number of iterations
307 tar_index : (int) : Index of the target. Default is 0
309 framerate = monitoring_freq / iters_time
310 strehl = self.
targettarget.get_strehl(tar_index)
311 etr = (total_iters - self.
iteriter) / framerate
312 print(
"%d \t %.3f \t %.3f\t %.1f \t %.1f" % (self.
iteriter + 1, strehl[0],
313 strehl[1], etr, framerate))
315 def loop(self, number_of_iter: int, *, monitoring_freq: int = 100,
316 compute_tar_psf: bool =
True, **kwargs):
317 """ Perform the AO loop for <number_of_iter> iterations
320 number_of_iter: (int) : Number of iteration that will be done
323 monitoring_freq: (int) : Monitoring frequency [frames]. Default is 100
325 compute_tar_psf : (bool) : If True (default), computes the PSF at each iteration
326 Else, only computes it each <monitoring_freq> frames
328 if not compute_tar_psf:
329 print(
"WARNING: Target PSF will be computed (& accumulated) only during monitoring"
332 print(
"----------------------------------------------------")
333 print(
"iter# | S.E. SR | L.E. SR | ETR (s) | Framerate (Hz)")
334 print(
"----------------------------------------------------")
338 if number_of_iter == -1:
340 self.
nextnext(compute_tar_psf=compute_tar_psf, **kwargs)
341 if ((self.
iteriter + 1) % monitoring_freq == 0):
342 if not compute_tar_psf:
343 self.
targettarget.comp_tar_image(0)
344 self.
targettarget.comp_strehl(0)
345 self.
_print_strehl_print_strehl(monitoring_freq, time.time() - t1, number_of_iter)
348 for _
in range(number_of_iter):
349 self.
nextnext(compute_tar_psf=compute_tar_psf, **kwargs)
350 if ((self.
iteriter + 1) % monitoring_freq == 0):
351 if not compute_tar_psf:
352 self.
targettarget.comp_tar_image(0)
353 self.
targettarget.comp_strehl(0)
354 self.
_print_strehl_print_strehl(monitoring_freq, time.time() - t1, number_of_iter)
357 print(
" loop execution time:", t1 - t0,
" (", number_of_iter,
"iterations), ",
358 (t1 - t0) / number_of_iter,
"(mean) ", number_of_iter / (t1 - t0),
"Hz")
361 """ Reset the simulation to return to its original state
363 self.
atmosatmos.reset_turbu()
364 self.
wfswfs.reset_noise()
365 for tar_index
in range(len(self.
configconfig.p_targets)):
366 self.
targettarget.reset_strehl(tar_index)
367 self.
dmsdms.reset_dm()
368 self.
rtcrtc.open_loop()
369 self.
rtcrtc.close_loop()
379 self, cb_count: int, sub_sample: int = 1, controller_index: int = 0,
380 tar_index: int = 0, see_atmos: bool =
True, cube_data_type: str =
None,
381 cube_data_file_path: str =
"", ncpa: int = 0, ncpa_wfs: np.ndarray =
None,
382 ref_slopes: np.ndarray =
None, ditch_strehl: bool =
True,
383 projection_matrix: np.ndarray =
None):
384 """ Used to record a synchronized circular buffer AO loop data.
387 cb_count: (int) : the number of iterations to record.
389 sub_sample: (int) : sub sampling of the data (default=1, I.e no subsampling)
391 controller_index: (int) :
393 tar_index: (int) : target number
395 see_atmos: (int) : used for the next function to enable or not the Atmos
397 cube_data_type: (int) : if specified ("tarPhase" or "psfse") returns the target phase or short exposure PSF data cube in the output variable
399 cube_data_file_path: (int) : if specified it will also save the target phase cube data (full path on the server)
401 ncpa: (int) : !!experimental!!!: Used only in the context of PYRWFS + NCPA compensation on the fly (with optical gain)
402 defines how many iters the NCPA refslopes are updates with the proper optical gain. Ex: if NCPA=10 refslopes will be updates every 10 iters.
404 ncpa_wfs: (int) : the ncpa phase as seen from the wfs array with dims = size of Mpupil
406 ref_slopes: (int) : the reference slopes to use.
408 ditch_strehl: (int) : resets the long exposure SR computation at the beginning of the Circular buffer (default= True)
410 projection_matrix : (np.ndarray) : projection matrix on modal basis to compute residual coefficients
413 slopes: (int) : the slopes CB
415 volts: (int) : the volts applied to the DM(s) CB
417 ai: (int) : the modal coefficient of the residual phase projected on the currently used modal Basis
419 psf_le: (int) : Long exposure PSF over the <cb_count> iterations (I.e SR is reset at the begining of the CB if ditch_strehl=True)
421 strehl_se_list: (int) : The SR short exposure evolution during CB recording
423 strehl_le_list: (int) : The SR long exposure evolution during CB recording
425 g_ncpa_list: (int) : the gain applied to the NCPA (PYRWFS CASE) if NCPA is set to True
427 cube_data: (int) : the tarPhase or psfse cube data (see cube_data_type)
441 for i
in range(len(self.
configconfig.p_targets)):
442 self.
targettarget.reset_strehl(i)
445 for j
in range(cb_count):
449 ncpa_diff = ref_slopes[
None, :]
450 ncpa_turbu = self.
calibrationcalibration.do_imat_phase(
451 controller_index, -ncpa_wfs[
None, :, :], noise=
False,
455 np.dot(ncpa_diff, ncpa_diff.T) / np.dot(
456 ncpa_turbu, ncpa_turbu.T)))
459 print(
'Warning NCPA ref slopes gain too high!')
460 g_ncpa_list.append(g_ncpa)
461 self.
rtcrtc.set_ref_slopes(-ref_slopes * g_ncpa)
463 g_ncpa_list.append(g_ncpa)
464 print(
'NCPA ref slopes gain: %4.3f' % g_ncpa)
465 self.
rtcrtc.set_ref_slopes(-ref_slopes / g_ncpa)
467 self.
atmosatmos.enable_atmos(see_atmos)
469 for t
in range(len(self.
configconfig.p_targets)):
470 self.
targettarget.comp_tar_image(t)
472 srse, srle, _, _ = self.
targettarget.get_strehl(tar_index)
473 sthrel_se_list.append(srse)
474 sthrel_le_list.append(srle)
475 if (j % sub_sample == 0):
476 if (projection_matrix
is not None):
477 ai_vector = self.
calibrationcalibration.compute_modal_residuals(
478 projection_matrix, selected_actus=self.
basisbasis.selected_actus)
479 if (ai_data
is None):
480 ai_data = np.zeros((len(ai_vector), int(cb_count / sub_sample)))
481 ai_data[:, k] = ai_vector
483 slopes_vector = self.
rtcrtc.get_slopes(controller_index)
484 if (slopes_data
is None):
485 slopes_data = np.zeros((len(slopes_vector),
486 int(cb_count / sub_sample)))
487 slopes_data[:, k] = slopes_vector
489 volts_vector = self.
rtcrtc.get_command(
491 if (volts_data
is None):
492 volts_data = np.zeros((len(volts_vector),
493 int(cb_count / sub_sample)))
494 volts_data[:, k] = volts_vector
497 if (cube_data_type ==
"tarPhase"):
498 dataArray = self.
targettarget.get_tar_phase(tar_index, pupil=
True)
499 elif (cube_data_type ==
"psfse"):
500 dataArray = self.
targettarget.get_tar_image(tar_index, expo_type=
"se")
502 raise ValueError(
"unknown dataData" % cube_data_type)
503 if (cube_data
is None):
504 cube_data = np.zeros((*dataArray.shape,
505 int(cb_count / sub_sample)))
506 cube_data[:, :, k] = dataArray
508 if (cube_data_file_path !=
""):
509 print(
"Saving tarPhase cube at: ", cube_data_file_path)
510 from astropy.io
import fits
as pf
511 pf.writeto(cube_data_file_path, cube_data, overwrite=
True)
513 psf_le = self.
targettarget.get_tar_image(tar_index, expo_type=
"le")
514 return slopes_data, volts_data, ai_data, psf_le, sthrel_se_list, sthrel_le_list, g_ncpa_list, cube_data
518 Extract and convert compass supervisor configuration parameters
519 into 2 dictionnaries containing relevant AO parameters
522 root: (object), COMPASS supervisor object to be parsed
525 2 dictionaries... See F. Vidal :)
531 Returns the so called S Pupil of COMPASS
534 s_pupil: (np.array) : S Pupil of COMPASS
536 return self.
configconfig.p_geom.get_spupil()
540 Returns the so called I Pupil of COMPASS
543 i_pupil: (np.array) : I Pupil of COMPASS
545 return self.
configconfig.p_geom.get_ipupil()
549 Returns the so called M Pupil of COMPASS
552 m_pupil: (np.array) : M Pupil of COMPASS
554 return self.
configconfig.p_geom.get_mpupil()
This class implements generic supervisor to handle compass simulation.
def __init__(self, config, *bool cacao=False)
Instantiates a CompassSupervisor object.
atmos
(AtmosComponent) : An AtmosComponent instance
def record_ao_circular_buffer(self, int cb_count, int sub_sample=1, int controller_index=0, int tar_index=0, bool see_atmos=True, str cube_data_type=None, str cube_data_file_path="", int ncpa=0, np.ndarray ncpa_wfs=None, np.ndarray ref_slopes=None, bool ditch_strehl=True, np.ndarray projection_matrix=None)
Used to record a synchronized circular buffer AO loop data.
None next(self, *bool move_atmos=True, int nControl=0, Iterable[int] tar_trace=None, Iterable[int] wfs_trace=None, bool do_control=True, bool apply_control=True, bool compute_tar_psf=True, bool compute_corono=True)
Iterates the AO loop, with optional parameters.
def _print_strehl(self, int monitoring_freq, float iters_time, int total_iters, *int tar_index=0)
def get_s_pupil(self)
Returns the so called S Pupil of COMPASS.
basis
(ModalBasis) : a ModalBasis instance (optimizer)
dms
(DmComponent) : A DmComponent instance
def _init_coronagraph(self)
rtc
(RtcComponent) : A Rtc component instance
target
(targetComponent) : A TargetComponent instance
cacao
(bool) : CACAO features enabled in the RTC
def get_m_pupil(self)
Returns the so called M Pupil of COMPASS.
def loop(self, int number_of_iter, *int monitoring_freq=100, bool compute_tar_psf=True, **kwargs)
Perform the AO loop for <number_of_iter> iterations.
calibration
(Calibration) : a Calibration instance (optimizer)
def get_i_pupil(self)
Returns the so called I Pupil of COMPASS.
modalgains
(ModalGains) : a ModalGain instance (optimizer) using CLOSE algorithm
def reset(self)
Reset the simulation to return to its original state.
close_modal_gains
(list of floats) : list of the previous values of the modal gains
wfs
(WfsComponent) : A WfsComponent instance
def export_config(self)
Extract and convert compass supervisor configuration parameters into 2 dictionnaries containing relev...
This class defines generic methods and behavior of a supervisor It is not intended to be instantiated...
iter
(int) : Frame counter
context
(CarmaContext) : a CarmaContext instance
config
(config) : Parameters structure
Numerical constants for shesha and config enumerations for safe-typing.
User layer for optimizing AO supervisor loop.