Source code for ecpi.simu.lib.context

"""Class :py:class:`.ContextSimulator` is a high-level `facade <https://en.wikipedia.org/wiki/Facade_pattern>`_ design pattern to centralize information and functionality
on the ECLAIRs instrument required for the simulation, like:

* instrument geometry
* detector effect
* position and attitude of ECLAIRs
* earth in FOV
* range energy
* time exposure

As this is information needed by many models we use the 
`singleton <https://en.wikipedia.org/wiki/Singleton_pattern>`_ 
design pattern to share it. So by default all the elements of the simulator:

* :py:class:`.SimuEclairsEnergyChannel`
* derived class of :py:class:`.AbstractModelSrc`
work with the singleton :py:class:`.GlobalContextSimulator.`

Class :py:class:`.ObservablePositionChange` implemented the 
`observer <https://en.wikipedia.org/wiki/Observer_pattern>`_ design pattern 
for sky sources sensitive to changes of satellite position. This architecture 
was introduced mainly to account the variation of CXB earth shadowing.

.. note:: 
   If all the sources become sensitive to changes in the satellite's position, 
   this structure is no longer relevant and a systematic update would be 
   more appropriate.
"""

import logging
import numpy as np
from ecpi.simu.lib.instru_x import SimuECLAIRsMaskProjection
from ecpi.common.instru.model_effect import ECLAIRsDetectorEffectDefault
from ecpi.common.mission.attitude import AttitudeECLAIRs
from ecpi.common.sky.extented_body import EarthInFov

logger = logging.getLogger(__name__)


[docs]class ObservablePositionChange: """Design pattern observer for change of position of satellite """ def __init__(self): """Init, create a empty list og observer """ self.observers = []
[docs] def add_observer(self, observer): """Add observer interested in a change of position of satellite :param observer: Instance of model source :type observer: AbstractModelSrc """ if observer not in self.observers: self.observers.append(observer) else: logger.debug('Failed to add: {}'.format(observer)) logger.info(f"{len(self.observers)} source(s) to update")
def _del_all_observer(self): """_del_all_observer """ self.observers = []
[docs] def del_observer(self, observer): """Delete observer from the list :param observer: Instance of model source :type observer: AbstractModelSrc """ try: self.observers.remove(observer) except ValueError: logger.error('Failed to remove: {}'.format(observer))
[docs] def notify_observer(self): """Notify all observer that position of satellite change """ # [o.update(self) for o in self.observers] [o.update() for o in self.observers]
[docs]class ContextSimulator(ObservablePositionChange): """Common data and functionalities for simulation Facade offering the data and common functionalities necessary to implement the simulation of sky sources through the ECLAIRs instrument .. note:: From CNES document "SVOM Coordinate SystemsandConventions Document", CSC-R-4.1-0011: For SVOM applications, J2000 and GCRF can be considered as identical, J2000 is considered the baseline frame for coordinates """ def __init__(self): """**Constructor** """ super().__init__() # high level context simulator and tools for simulator self._sim_geom = SimuECLAIRsMaskProjection() self._mdl_effect = ECLAIRsDetectorEffectDefault() self._ecl_att = AttitudeECLAIRs() self._earth_fov = EarthInFov() # Parameters of simulation self._idx_chan = 0 self._num_chan = self._mdl_effect._channel[0] """energy limits in keV""" self._e_min = 0 self._e_max = 0 """satellite unit quaternion""" self._quat_att = None """position [X, Y, Z] of SVOM in km in J2000""" self._pos = [0, 0, 0] self._id_pos = -1 """velocity [VX, VY, VZ] of SVOM in km/s in J2000""" self._vel = [0, 0, 0] """observation start time in s""" self._t_start = 0 """time duration in s""" self._duration = 0 # Getter @property def duration(self): """ :getter: return duration in s of simulation :type: float """ return self._duration @property def t_start(self): """ :getter: return time start of simulation """ return self._t_start @property def eclairs_attitude(self): """ :getter: return object attitude of ECLAIRs instrument :type: AttitudeECLAIRs """ return self._ecl_att @property def idx_chan(self): """ :getter: return current energy channel index :type: int """ return self._idx_chan @property def mdl_effect(self): """ :getter: return object of instrument effect :type: ECLAIRsDetectorEffect """ return self._mdl_effect @property def pos_sat(self): """ :getter: position of SVOM in km in J2000 :type: [X, Y, Z] """ return self._pos @property def energy_range(self): """ :getter: return min max in keV of current energy channel :type: float, float """ return self._e_min, self._e_max @property def earth_pos_fov_unit(self): """ :getter: return unit vector of earth in FOV :type: [X, Y, Z] """ return self._earth_fov.body_xyz() @property def earth_limb(self): """ :getter: size in degree of earth in FOV :type: degree """ return self._earth_fov.limb_angle() # TODO: peut etre passer en _earth_fov en publique, à voir ...
[docs] def open_fov_map(self): """ :return: Open FOV map :rtype: numpy array """ return self._earth_fov.compute_openfov_map()
[docs] def earth_frac(self): """ :return: fraction (between 0 and 1) of earth in FOV :rtype: float """ self._earth_fov.compute_openfov_map() return self._earth_fov.compute_occultation_frac()
[docs] def is_in_fov(self): """ :return: True if earth is in FOV :rtype: bool """ return self._earth_fov.is_in_fov()
# Setter def _set_energy_band(self, e_min, e_max): """Set the energy lower and upper limit for sources models classes :param e_min: energy lower limit in keV :type e_min: float :param e_max: energy upper limit in keV :type e_max: float """ self._e_min = e_min self._e_max = e_max
[docs] def set_duration(self, duration): """ :param duration: simulation time in s :type duration: float """ self._duration = duration
[docs] def set_t_start(self, t_start): """ :param t_start: start time of the simulated observation in s from mjdref :type t_start: float """ self._t_start = t_start
[docs] def set_sim_geom(self, sim_pts): """ :param sim_pts: TBD :type sim_pts: TBD """ self._sim_geom = sim_pts
[docs] def set_mdl_effect(self, mdl_effect): """ :param mdl_effect: object defined instrument effect :type mdl_effect: ECLAIRsDetectorEffect """ self._mdl_effect = mdl_effect
[docs] def set_quaternion_svom(self, quater): """ Calculates the attitude of the ECLAIRs instrument with the attitude of the SVOM satellite. :param quater: quaternion defined attitude of SVOM satellite :type quater: [q0, q1, q2, q3] """ self._ecl_att.set_attitude_svom_quater(quater) self._earth_fov.set_quater_svom(quater)
[docs] def set_idx_chan(self, num_chan): """Set energy channel index :param idx_chan: energy channel index :type idx_chan: int """ idx_chan = num_chan - self._mdl_effect._channel[0] self._num_chan = num_chan self._idx_chan = idx_chan self._e_min = self._mdl_effect.chan_boundary[idx_chan] self._e_max = self._mdl_effect.chan_boundary[idx_chan + 1]
[docs] def set_pos_sat(self, pos): """ Change the position of SVOM satellite and call method :py:meth:`.update` for all sky recorded sources with :py:meth:`.add_observer` :param pos: satellite position in km in J2000 frame :type pos: array 3 float """ if not isinstance(pos, np.ndarray): self._pos = np.array(pos) else: self._pos = pos self._earth_fov.set_pos_sat_time(self._pos) self._id_pos += 1 self.notify_observer()
[docs]class GlobalContextSimulator(ContextSimulator): """Singleton version of class ContextSimulator """ instance = None def __new__(cls, *args, **kargs): """ used singleton design pattern, ie only one instance of class ManageProcessusEcpi """ if cls.instance is None: return object.__new__(cls, *args, **kargs) else: return cls.instance def __init__(self): # REMEMBER : self is the return of __new__ if GlobalContextSimulator.instance is None: super().__init__() GlobalContextSimulator.instance = self
[docs] def reset_all(self): """ Reset all observers and initialize instrument effect with default object """ self.reset_detec_effect() self.reset_obs()
[docs] def reset_observer(self): """ Reset all observers of satellite position change """ self._del_all_observer()
[docs] def reset_detec_effect(self): """ Initialize instrument effect with default object .. note:: Some tests reduce channel or modify attribut _mdl_effect """ self._mdl_effect = ECLAIRsDetectorEffectDefault()