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