"""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()