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 (see The Instrument Model Database (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()
.
The following example uses the PTEP IMO to show how to use the API:
import litebird_sim as lbs
# The location of the PTEP IMO is stored in the constant
# PTEP_IMO_LOCATION
imo = lbs.Imo(flatfile_location=lbs.PTEP_IMO_LOCATION)
det = lbs.DetectorInfo.from_imo(
imo=imo,
url="/releases/vPTEP/satellite/LFT/L1-040/"
"000_000_003_QA_040_T/detector_info",
)
print(f"The bandcenter for {det.name} is {det.bandcenter_ghz} GHz")
freqch = lbs.FreqChannelInfo.from_imo(
imo=imo,
url="/releases/vPTEP/satellite/LFT/L1-040/channel_info",
)
print(
f"The average bandcenter for {freqch.channel} "
f"is {freqch.bandcenter_ghz} GHz"
)
The bandcenter for 000_000_003_QA_040_T is 40.0 GHz
The average bandcenter for L1-040 is 40.0 GHz
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=lbs.PTEP_IMO_LOCATION)
# This UUID refers to a 140 GHz "channel_info" object.
ch = imo.query("/data_files/463e9ea9-c1f0-484d-9bfd-05092851d8f4")
metadata = ch.metadata
print("Here are the contents of the mock IMO:")
print(f'Channel: {metadata["channel"]}')
detector_names = metadata["detector_names"]
print(f'There are {len(detector_names)} detectors')
print("Here are the first 5 of them:")
for name, obj in list(zip(metadata["detector_names"],
metadata["detector_objs"]))[0:5]:
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: M1-140
There are 366 detectors
Here are the first 5 of them:
001_002_030_00A_140_T: band center at 140.0 GHz
001_002_030_00A_140_B: band center at 140.0 GHz
001_002_031_15B_140_T: band center at 140.0 GHz
001_002_031_00B_140_B: band center at 140.0 GHz
001_002_022_15A_140_T: band center at 140.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. The parameter random_seed
is mandatory for the Simulation
constructor.
# This is file "det_list1.toml"
# Mandatory parameter
[simulation]
random_seed = 12345
# 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/34b46c91-e196-49b9-9d44-d69b4827f751"
# Append two 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/463e9ea9-c1f0-484d-9bfd-05092851d8f4"
# 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/463e9ea9-c1f0-484d-9bfd-05092851d8f4"
# This implies num_of_detectors_from_channel == 1
use_only_one_boresight_detector = true
detector_name = "foo_boresight"
# Append one more 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
# Add one last element
[[detectors]]
# Take the parameters from the IMO but fix one parameter by hand
detector_info_obj = "/data_files/34b46c91-e196-49b9-9d44-d69b4827f751"
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 6 detectors:
from pathlib import Path
import litebird_sim as lbs
imo = lbs.Imo(flatfile_location=lbs.PTEP_IMO_LOCATION)
# Tell Simulation to use the PTEP IMO and 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. 000_000_003_QA_040_T: band center at 40.0 GHz
2. 001_002_030_00A_140_T: band center at 140.0 GHz
3. 001_002_030_00A_140_B: band center at 140.0 GHz
4. foo_boresight: band center at 140.0 GHz
5. planck30GHz: band center at 28.4 GHz
6. 000_000_003_QA_040_T: band center at 40.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. Note
again the mandatory parameter random_seed.
# This is file "det_list2.toml"
# Mandatory parameter
[simulation]
random_seed = 12345
[simulation1]
# Detectors to be used for the first simulation
[[simulation1.detectors]]
detector_info_obj = "/data_files/899ba8cc-789c-47bf-9bb1-40f61b25b3a9"
[simulation2]
# Detectors to be used for the second simulation
[[simulation2.detectors]]
detector_info_obj = "/data_files/e4401060-9233-4b14-a642-c00293c99c70"
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=lbs.PTEP_IMO_LOCATION)
# Tell the Simulation object that we want to use the PTEP 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:
- 000_000_004_QB_040_B
Detectors to be used in the second simulation:
- 000_000_004_QB_040_T
API reference¶
- class litebird_sim.detectors.DetectorInfo(name: str = '', wafer: str | None = None, pixel: int | None = None, pixtype: str | None = None, channel: str | None = 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, band_freqs_ghz: None | ndarray = None, band_weights: None | ndarray = None, net_ukrts: float = 0.0, pol_sensitivity_ukarcmin: float = 0.0, fknee_mhz: float = 0.0, fmin_hz: float = 0.0, alpha: float = 0.0, pol: str | None = None, orient: str | None = None, quat: Any = (0.0, 0.0, 0.0, 1.0))¶
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
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. This is the noise per sample to be used when adding noise to the timelines. The default is 0.0
pol_sensitivity_ukarcmin (-) – The detector sensitivity in microK_arcmin. This value considers the effect of cosmic ray loss, repointing maneuvers, etc., and other issues that cause loss of integration time. Therefore, it should not be used with the functions that add noise to the timelines. 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
band_freqs_ghz (-) – band sampled frequencies, in GHz. The default is None
band_weights (-) – band profile. The default is None
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¶
- band_freqs_ghz: None | ndarray = None¶
- band_weights: None | ndarray = None¶
- bandcenter_ghz: float = 0.0¶
- bandwidth_ghz: float = 0.0¶
- channel: str | None = 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
band_freqs_ghz
band_weights
sampling_rate_hz
fwhm_arcmin
ellipticity
net_ukrts
pol_sensitivity_ukarcmin
fknee_mhz
fmin_hz
alpha
pol
orient
quat
- static from_imo(imo: Imo, url: 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: str | None = None¶
- pixel: int | None = None¶
- pixtype: str | None = None¶
- pol: str | None = None¶
- pol_sensitivity_ukarcmin: float = 0.0¶
- quat: Any = (0.0, 0.0, 0.0, 1.0)¶
- sampling_rate_hz: float = 0.0¶
- wafer: str | None = None¶
- class litebird_sim.detectors.FreqChannelInfo(bandcenter_ghz: float, channel: Optional[str] = None, bandwidth_ghz: float = 0.0, band_freqs_ghz: Optional[numpy.ndarray] = None, band_weights: Optional[numpy.ndarray] = None, 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¶
- band_freqs_ghz: None | ndarray = None¶
- band_weights: None | ndarray = None¶
- bandcenter_ghz: float¶
- bandwidth_ghz: float = 0.0¶
- channel: str | None = None¶
- detector_names: List[str]¶
- detector_objs: List[UUID]¶
- fknee_mhz: float = 0.0¶
- fmin_hz: float = 1e-05¶
- static from_dict(dictionary: Dict[str, Any])¶
- fwhm_arcmin: float = 0.0¶
- get_boresight_detector(name='mock') 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]¶
- channel_objs: List[UUID]¶
- static from_dict(dictionary: Dict[str, Any])¶
- 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]¶
- wafer_space_cm: float = 0.0¶
- litebird_sim.detectors.detector_list_from_parameters(imo: Imo, definition_list: List[Any]) List[DetectorInfo] ¶
- litebird_sim.detectors.url_to_uuid(url: str) UUID ¶