Detectors, channels, and instruments¶
The core of the LiteBIRD instruments is the set of detectors that perform the measurement of the electromagnetic power entering through the telescope. Several simulation modules revolve around the concept of «detector», «frequency channel», and «instrument», and the LiteBIRD Simulation Framework implements a few classes and functions to handle the information associated with them.
Consider this example:
import litebird_sim as lbs
detectors = [
lbs.DetectorInfo(
name="Dummy detector #1",
net_ukrts=100.0,
bandcenter_ghz=123.4,
bandwidth_ghz=5.6,
),
lbs.DetectorInfo(
name="Dummy detector #2",
net_ukrts=110.0,
fwhm_arcmin=65.8,
),
]
# Now simulate the behavior of the two detectors
# …
Here you see that the DetectorInfo
class can be used to
store information related to single detectors, and that you can choose
which information provide for each detector: the example specifies
bandshape information only for the first detector (Dummy detector
#1
), but not for the other. Missing information is usually
initialized with zero or a sensible default. The simulation modules
provided by the framework typically expect one or more
DetectorInfo
objects to be passed as input.
Detectors are grouped according to their nominal central frequency in
frequency channels; there are several frequency channels in the
three LiteBIRD instruments (LFT, MFT, HFT), and sometimes one does not
need to simulate each detector within a frequency channel, but only
the channel as a whole. In this case there is the
FreqChannelInfo
class, which can produce a mock
DetectorInfo
object by taking out the «average» information
of the frequency channel:
import litebird_sim as lbs
chinfo = lbs.FreqChannelInfo(
bandcenter_ghz=40.0,
net_channel_ukrts=40.0, # Taken from Sugai et al. 2020 (JLTP)
number_of_detectors=64, # 32 pairs
)
# Return a "mock" detector that is representative of the
# frequency channel
mock_det = chinfo.get_boresight_detector(name="mydet")
# Look, ma, a detector!
assert isinstance(mock_det, lbs.DetectorInfo)
assert mock_det.name == "mydet"
print("The NET of one detector at 40 GHz is {0:.1f} µK·√s"
.format(mock_det.net_ukrts))
The NET of one detector at 40 GHz is 320.0 µK·√s
Finally, the Instrument
class holds information about one of
the three instruments onboard the LiteBIRD spacecraft (LFT, MFT, and
HFT).
Reading from the IMO¶
The way information about detectors and frequency channels is stored
in the IMO closely match the DetectorInfo
and
FreqChannelInfo
classes. In fact, they can be retrieved
easily from the IMO using the static methods
DetectorInfo.from_imo()
and FreqChannelInfo.from_imo()
:
import litebird_sim as lbs
# You must have configured the IMO before using this!
imo = lbs.Imo()
det = lbs.DetectorInfo.from_imo(
imo=imo,
url="/releases/v1.0/satellite/LFT/L1-040/"
"L00_008_QA_040T/detector_info",
)
freqch = lbs.FreqChannelInfo.from_imo(
imo=imo,
url="/releases/v1.0/satellite/LFT/L1-040/channel_info",
)
Detectors in parameter files¶
It is often the case that the list of detectors to simulate must be read from a parameter file. There are several situations that typically need to be accomodated:
In some simulations, you just need to simulate one detector (whose parameters can be taken either from the IMO or from the parameter file itself);
In other cases, you want to simulate all the detectors in one or more frequency channels: in this case, you would like to avoid specifying them one by one!
In other cases, you might want to specify just a subset
Finally, you might base your simulation on the IMO definition of a detector/channel but twiddle a bit with some parameters.
The LiteBIRD simulation framework provides a way to read a set of
detectors/channels from a dictionary, which can be used to build a
list of DetectorInfo
/FreqChannelInfo
objects.
In the following examples, we will refer to a «mock» IMO, which
contains information about a few fake detectors. It’s included in the
source distribution of the framework, under the directory
litebird_sim/test/mock_imo
, and it defines one frequency channel
with four detectors. Here is a summary of its contents:
from pathlib import Path
import litebird_sim as lbs
# This ensures that the mock IMO can be found when generating this
# HTML page
imo = lbs.Imo(
flatfile_location=Path(".").parent / ".." / "test" / "mock_imo",
)
# This UUID refers to an object specifying the information for
# a mock frequency channel containing 4 detectors
ch = imo.query("/data_files/ff087ba3-d973-4dc3-b72b-b68abb979a90")
metadata = ch.metadata
print("Here are the contents of the mock IMO:")
print(f'Channel: {metadata["channel"]}')
print("Detectors in this channel:")
for name, obj in zip(metadata["detector_names"],
metadata["detector_objs"]):
det_obj = imo.query(obj)
det_metadata = det_obj.metadata
bandcenter_ghz = det_metadata["bandcenter_ghz"]
print(f' {name}: band center at {bandcenter_ghz:.1f} GHz')
Here are the contents of the mock IMO:
Channel: 65 GHz
Detectors in this channel:
foo1: band center at 65.0 GHz
foo2: band center at 66.0 GHz
foo3: band center at 67.0 GHz
foo4: band center at 68.0 GHz
Now, let’s turn back to the problem of specifying a set of detectors in a parameter file. The following TOML file shows some of the possibilities granted by the framework:
# This is file "det_list1.toml"
# In this example we list the detectors to use in the simulation
# using one of TOML's features: lists of elements. They are
# indicated using double square brackets, and each occurrence
# appends a new item to the end of the list.
# First element
[[detectors]]
# Take the parameters for the detector from the IMO;
# here we specify the UUID of the object
detector_info_obj = "/data_files/78fe75f1-a011-44b6-86dd-445dc9634416"
# Append a few more elements
[[detectors]]
# Take the parameters for the set of detectors from the channel
# information in the IMO. As above, we use an UUID
channel_info_obj = "/data_files/ff087ba3-d973-4dc3-b72b-b68abb979a90"
# Just generate two detectors (default is to generate all the
# detectors in this frequency channel, which contains *four*
# detectors). This is useful when debugging, because it can
# drastically reduce the amount of data to simulate
num_of_detectors_from_channel = 2
# Append one more element
[[detectors]]
# Unlike in the previous example, here we just want to generate *one*
# mock detector associated with a specified frequency channel. This
# detector will be aligned with the boresight of the focal plane, and
# it will use the «average» detector parameters for this channel
channel_info_obj = "/data_files/ff087ba3-d973-4dc3-b72b-b68abb979a90"
# This implies num_of_detectors_from_channel == 1
use_only_one_boresight_detector = true
detector_name = "foo_boresight"
# Add one last element
[[detectors]]
# Set up every parameter manually
name = "planck30GHz"
channel = "30 GHz"
fwhm_arcmin = 33.10
fknee_mhz = 113.9
bandwidth_ghz = 9.89
bandcenter_ghz = 28.4
sampling_rate_hz = 32.5
[[detectors]]
# Take the parameters from the IMO but fix one parameter by hand
detector_info_obj = "/data_files/78fe75f1-a011-44b6-86dd-445dc9634416"
sampling_rate_hz = 1.0 # Fix this parameter
If the TOML file you are using in your simulation follows this
structure, you can use the function
detector_list_from_parameters()
, which parses the parameters
and uses an Imo
object to build a list of
DetectorInfo
objects.
The following code will read the TOML file above and produce a list of 5 detectors:
from pathlib import Path
import litebird_sim as lbs
# Load a mock IMO that actually defines the UUID listed above
imo = lbs.Imo(
flatfile_location=Path(".").parent / ".." / "test" / "mock_imo",
)
# Tell Simulation to load the TOML file shown above
sim = lbs.Simulation(imo=imo, parameter_file="det_list1.toml")
det_list = lbs.detector_list_from_parameters(
imo=sim.imo,
definition_list=sim.parameters["detectors"],
)
for idx, det in enumerate(det_list):
print(f"{idx + 1}. {det.name}: band center at {det.bandcenter_ghz} GHz")
1. foo1: band center at 65.0 GHz
2. foo1: band center at 65.0 GHz
3. foo2: band center at 66.0 GHz
4. foo_boresight: band center at 65.0 GHz
5. planck30GHz: band center at 28.4 GHz
6. foo1: band center at 65.0 GHz
You are not forced to use detectors
as the name of the parameter
in the TOML file, as detector_list_from_parameters()
accepts a
generic list. As an example, consider the following TOML file:
# This is file "det_list2.toml"
[simulation1]
# Detectors to be used for the first simulation
[[simulation1.detectors]]
detector_info_obj = "/data_files/78fe75f1-a011-44b6-86dd-445dc9634416"
[simulation2]
# Detectors to be used for the second simulation
[[simulation2.detectors]]
detector_info_obj = "/data_files/58db7186-3ee7-49e5-8605-4a5ed084f99e"
Its purpose is to provide the parameters for a two-staged simulation,
and each of them requires its own list of detectors. For this purpose,
it uses the TOML syntax [[simulation1.detectors]]
to specify that
the detectors
list belongs to the section simulation1
, and
similarly for [[simulation2.detectors]]
. Here is a Python script
that interprets the TOML file and prints the detectors to be used at
each stage of the simulation:
from pathlib import Path
import litebird_sim as lbs
imo = lbs.Imo(
flatfile_location=Path(".").parent / ".." / "test" / "mock_imo",
)
sim = lbs.Simulation(imo=imo, parameter_file="det_list2.toml")
det_list1 = lbs.detector_list_from_parameters(
imo=sim.imo,
definition_list=sim.parameters["simulation1"]["detectors"],
)
det_list2 = lbs.detector_list_from_parameters(
imo=sim.imo,
definition_list=sim.parameters["simulation2"]["detectors"],
)
print("Detectors to be used in the first simulation:")
for det in det_list1:
print(f"- {det.name}")
print("Detectors to be used in the second simulation:")
for det in det_list2:
print(f"- {det.name}")
Detectors to be used in the first simulation:
- foo1
Detectors to be used in the second simulation:
- foo2
API reference¶
-
class
litebird_sim.detectors.
DetectorInfo
(name: str = '', wafer: Optional[str] = None, pixel: Optional[int] = None, pixtype: Optional[str] = None, channel: Optional[str] = None, sampling_rate_hz: float = 0.0, fwhm_arcmin: float = 0.0, ellipticity: float = 0.0, bandcenter_ghz: float = 0.0, bandwidth_ghz: float = 0.0, net_ukrts: float = 0.0, fknee_mhz: float = 0.0, fmin_hz: float = 0.0, alpha: float = 0.0, pol: Optional[str] = None, orient: Optional[str] = None, quat: Any = array([0., 0., 0., 1.]))¶ Bases:
object
A class wrapping the basic information about a detector.
This is a data class that encodes the basic properties of a LiteBIRD detector. It can be initialized in three ways:
Through the default constructor; all its parameters are optional, but you probably want to specify at least name and sampling_rate_hz:
det = DetectorInfo(name="dummy", sampling_rate_hz=10.0)
Through the class method
from_dict()
, which takes a dictionary as input.Through the class method
from_imo()
, which reads the definition of a detector from the LiteBIRD Instrument Model (see theImo
class).
- Parameters
name (-) – the name of the detector; the default is the empty string
wafer (-) – The name of the wafer hosting the detector, e.g.
H00
,L07
, etc. The default is Nonepixel (-) – The number of the pixel within the wafer. The default is None
pixtype (-) – The type of the pixel, e.g.,
HP1
,LP3
, etc. The default is Nonechannel (-) – The channel. The default is None
sampling_rate_hz (-) – The sampling rate of the ADC associated with this detector. The default is 0.0
bandwidth_ghz (-) – The bandwidth of the channel, in GHz
bandcenter_ghz (-) – The central frequency of the channel, in GHz
fwhm_arcmin (-) – float): The Full Width Half Maximum of the radiation pattern associated with the detector, in arcminutes. The default is 0.0
ellipticity (-) – The ellipticity of the radiation pattern associated with the detector. The default is 0.0
net_ukrts (-) – The noise equivalent temperature of the signal produced by the detector in nominal conditions, expressed in μK/√s.. The default is 0.0
bandcenter_ghz – The center frequency of the detector, in GHz. The default is 0.0
bandwidth_ghz – The bandwidth of the detector, in GHz. The default is 0.0
fknee_mhz (-) – The knee frequency between the 1/f and the white noise components in nominal conditions, in mHz. The default is 0.0
fmin_hz (-) – The minimum frequency of the noise when producing synthetic noise, in Hz. The default is 0.0
alpha (-) – The slope of the 1/f component of the noise in nominal conditions. The default is 0.0
pol (-) – The polarization of the detector (
T
/B
). The default is Noneorient (-) – The orientation of the detector (
Q
/U
). The default is Nonequat (-) – The quaternion expressing the rotation from the detector reference frame to the boresight reference frame. The default is no rotation at all, i.e., the detector is aligned with the boresight direction.
-
alpha
: float = 0.0¶
-
bandcenter_ghz
: float = 0.0¶
-
bandwidth_ghz
: float = 0.0¶
-
channel
: Optional[str] = None¶
-
ellipticity
: float = 0.0¶
-
fknee_mhz
: float = 0.0¶
-
fmin_hz
: float = 0.0¶
-
static
from_dict
(dictionary: Dict[str, Any])¶ Create a detector from the contents of a dictionary
The parameter dictionary must contain one key for each of the fields in this dataclass:
name
wafer
pixel
pixtype
channel
bandcenter_ghz
bandwidth_ghz
sampling_rate_hz
fwhm_arcmin
ellipticity
net_ukrts
fknee_mhz
fmin_hz
alpha
pol
orient
quat
-
static
from_imo
(imo: litebird_sim.imo.imo.Imo, url: Union[uuid.UUID, str])¶ Create a DetectorInfo object from a definition in the IMO
The url must either specify a UUID or a full URL to the object.
Example:
import litebird_sim as lbs imo = Imo() det = DetectorInfo.from_imo( imo=imo, url="/releases/v1.0/satellite/LFT/L1-040/L00_008_QA_040T/detector_info", )
-
fwhm_arcmin
: float = 0.0¶
-
name
: str = ''¶
-
net_ukrts
: float = 0.0¶
-
orient
: Optional[str] = None¶
-
pixel
: Optional[int] = None¶
-
pixtype
: Optional[str] = None¶
-
pol
: Optional[str] = None¶
-
quat
: Any = array([0., 0., 0., 1.])¶
-
sampling_rate_hz
: float = 0.0¶
-
wafer
: Optional[str] = None¶
-
class
litebird_sim.detectors.
FreqChannelInfo
(bandcenter_ghz: float, channel: Union[str, NoneType] = None, bandwidth_ghz: float = 0.0, net_detector_ukrts: float = 0.0, net_channel_ukrts: float = 0.0, pol_sensitivity_channel_ukarcmin: float = 0.0, sampling_rate_hz: float = 0.0, fwhm_arcmin: float = 0.0, fknee_mhz: float = 0.0, fmin_hz: float = 1e-05, alpha: float = 1.0, number_of_detectors: int = 0, detector_names: List[str] = <factory>, detector_objs: List[uuid.UUID] = <factory>)¶ Bases:
object
-
alpha
: float = 1.0¶
-
bandcenter_ghz
: float = None¶
-
bandwidth_ghz
: float = 0.0¶
-
channel
: Optional[str] = None¶
-
detector_names
: List[str] = None¶
-
detector_objs
: List[UUID] = None¶
-
fknee_mhz
: float = 0.0¶
-
fmin_hz
: float = 1e-05¶
-
static
from_dict
(dictionary: Dict[str, Any])¶
-
static
from_imo
(imo: litebird_sim.imo.imo.Imo, url: Union[uuid.UUID, str])¶
-
fwhm_arcmin
: float = 0.0¶
-
get_boresight_detector
(name='mock') → litebird_sim.detectors.DetectorInfo¶
-
net_channel_ukrts
: float = 0.0¶
-
net_detector_ukrts
: float = 0.0¶
-
number_of_detectors
: int = 0¶
-
pol_sensitivity_channel_ukarcmin
: float = 0.0¶
-
sampling_rate_hz
: float = 0.0¶
-
-
class
litebird_sim.detectors.
InstrumentInfo
(name: str = '', boresight_rotangle_rad: float = 0.0, spin_boresight_angle_rad: float = 0.0, spin_rotangle_rad: float = 0.0, hwp_rpm: float = 0.0, number_of_channels: int = 0, channel_names: List[str] = <factory>, channel_objs: List[uuid.UUID] = <factory>, wafer_names: List[str] = <factory>, wafer_space_cm: float = 0.0)¶ Bases:
object
-
bore2spin_quat
= array([0., 0., 0., 1.])¶
-
boresight_rotangle_rad
: float = 0.0¶
-
channel_names
: List[str] = None¶
-
channel_objs
: List[UUID] = None¶
-
static
from_dict
(dictionary: Dict[str, Any])¶
-
static
from_imo
(imo: litebird_sim.imo.imo.Imo, url: Union[uuid.UUID, str])¶
-
hwp_rpm
: float = 0.0¶
-
name
: str = ''¶
-
number_of_channels
: int = 0¶
-
spin_boresight_angle_rad
: float = 0.0¶
-
spin_rotangle_rad
: float = 0.0¶
-
wafer_names
: List[str] = None¶
-
wafer_space_cm
: float = 0.0¶
-
-
litebird_sim.detectors.
detector_list_from_parameters
(imo: litebird_sim.imo.imo.Imo, definition_list: List[Any]) → List[litebird_sim.detectors.DetectorInfo]¶