Source code for simu.lib.context

"""Context is a high-level facade 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 design pattern to share it.
"""

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 Observable: """Designe pattern observer """ def __init__(self): """Init Observable """ self.observers = []
[docs] def add_observer(self, observer): """ :param observer: """ 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): """ :param observer: """ try: self.observers.remove(observer) except ValueError: logger.error('Failed to remove: {}'.format(observer))
[docs] def notify_observer(self): """notify_observer """ # [o.update(self) for o in self.observers] [o.update() for o in self.observers]
[docs]class ContextSimulator(Observable): """Defines a general simulation context for ECLAIRs observations """ 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 """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): """duration """ return self._duration @property def t_start(self): """t_start """ return self._t_start @property def eclairs_attitude(self): """eclairs_attitude """ return self._ecl_att @property def idx_chan(self): """idx_chan """ return self._idx_chan @property def pos_sat(self): """pos_sat """ return self._pos @property def energy_range(self): """energy_range """ return self._e_min, self._e_max @property def earth_pos_fov_unit(self): """earth_pos_fov_unit """ return self._earth_fov.body_xyz() @property def earth_limb(self): """earth_limb """ return self._earth_fov.limb_angle() #TODO: peut etre passer en _earth_fov en publique, à voir ...
[docs] def open_fov_map(self): return self._earth_fov.compute_openfov_map()
[docs] def earth_frac(self): self._earth_fov.compute_openfov_map() return self._earth_fov.compute_occultation_frac()
[docs] def is_in_fov(self): 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 .. warning:: must be renamed as _set_energy_band :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: """ self._sim_geom = sim_pts
[docs] def set_mdl_effect(self, mdl_effect): """ :param mdl_effect: """ self._mdl_effect = mdl_effect
[docs] def set_quaternion_svom(self, quater): """ ..note :: proposal: add switch to allow the user to choose between specifying a quaternion or an attitude. Or split functions. """ self._ecl_att.set_attitude_svom_quater(quater) self._earth_fov.set_quater_svom(quater)
[docs] def set_idx_chan(self, idx_chan): """Set energy channel index :param idx_chan: energy channel index :type idx_chan: int """ 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): """ :param pos: satellite position in km in GCRS 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): """Create global simulation context. """ 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): # REMENBER : self is the return of __new__ if GlobalContextSimulator.instance is None: super().__init__() GlobalContextSimulator.instance = self
[docs] def reset_all(self): self.reset_detec_effect() self.reset_obs()
[docs] def reset_observer(self): self._del_all_observer()
[docs] def reset_detec_effect(self): # JMC some tests reduce chanel or modify ECLAIRsDetectorEffect self._mdl_effect = ECLAIRsDetectorEffectDefault()