mwa_hyperdrive/cli/common/beam/
mod.rs1#[cfg(test)]
6mod tests;
7
8use std::{path::PathBuf, str::FromStr};
9
10use clap::Parser;
11use log::{debug, trace};
12use ndarray::prelude::*;
13use serde::{Deserialize, Serialize};
14
15use super::{InfoPrinter, Warn};
16use crate::{
17 beam::{Beam, BeamError, BeamType, Delays, FEEBeam, NoBeam, BEAM_TYPES_COMMA_SEPARATED},
18 io::read::VisInputType,
19};
20
21lazy_static::lazy_static! {
22 static ref BEAM_TYPE_HELP: String =
23 format!("The beam model to use. Supported models: {}. Default: {}", *BEAM_TYPES_COMMA_SEPARATED, BeamType::default());
24
25 static ref NO_BEAM_HELP: String =
26 format!("Don't apply a beam response when generating a sky model. The default is to use the {} beam.", BeamType::default());
27
28 static ref BEAM_FILE_HELP: String =
29 format!("The path to the HDF5 MWA FEE beam file. Only useful if the beam type is 'fee'. If not specified, this must be provided by the MWA_BEAM_FILE environment variable.");
30}
31
32#[derive(Parser, Debug, Clone, Default, Serialize, Deserialize)]
33pub(crate) struct BeamArgs {
34 #[clap(short, long, help_heading = "BEAM", help = BEAM_TYPE_HELP.as_str())]
35 pub(crate) beam_type: Option<String>,
36
37 #[clap(long, conflicts_with("beam-type"), help_heading = "BEAM", help = NO_BEAM_HELP.as_str())]
38 #[serde(default)]
39 pub(crate) no_beam: bool,
40
41 #[clap(long, multiple_values(true), help_heading = "BEAM")]
44 pub(crate) delays: Option<Vec<u32>>,
45
46 #[clap(long, help_heading = "BEAM")]
49 #[serde(default)]
50 pub(crate) unity_dipole_gains: bool,
51
52 #[clap(long, help_heading = "BEAM", help = BEAM_FILE_HELP.as_str())]
55 pub(crate) beam_file: Option<PathBuf>,
56}
57
58impl BeamArgs {
59 pub(crate) fn merge(self, other: Self) -> Self {
60 Self {
61 beam_type: self.beam_type.or(other.beam_type),
62 no_beam: self.no_beam || other.no_beam,
63 delays: self.delays.or(other.delays),
64 unity_dipole_gains: self.unity_dipole_gains || other.unity_dipole_gains,
65 beam_file: self.beam_file.or(other.beam_file),
66 }
67 }
68
69 pub(crate) fn parse(
70 self,
71 total_num_tiles: usize,
72 data_dipole_delays: Option<Delays>,
73 dipole_gains: Option<Array2<f64>>,
74 input_data_type: Option<VisInputType>,
75 ) -> Result<Box<dyn Beam>, BeamError> {
76 let Self {
77 beam_type,
78 no_beam,
79 delays: user_dipole_delays,
80 unity_dipole_gains,
81 beam_file,
82 } = self;
83
84 let mut printer = InfoPrinter::new("Beam info".into());
85 debug!("Beam file: {beam_file:?}");
86
87 let beam_type = match (
88 no_beam,
89 beam_type.as_deref(),
90 beam_type
91 .as_deref()
92 .and_then(|b| BeamType::from_str(b).ok()),
93 ) {
94 (true, _, _) => BeamType::None,
95 (false, None, _) => BeamType::default(),
96 (false, Some(_), Some(b)) => b,
97 (false, Some(s), None) => return Err(BeamError::Unrecognised(s.to_string())),
98 };
99
100 let beam: Box<dyn Beam> = match beam_type {
101 BeamType::None => {
102 debug!("Setting up a \"NoBeam\" object");
103 printer.push_line("Not using any beam responses".into());
104 Box::new(NoBeam {
105 num_tiles: total_num_tiles,
106 })
107 }
108
109 BeamType::FEE => {
110 debug!("Setting up a FEE beam object");
111 printer.push_line("Type: FEE".into());
112
113 let mut dipole_delays = match user_dipole_delays {
114 Some(d) => Some(Delays::parse(d)?),
115 None => data_dipole_delays,
116 }
117 .ok_or(BeamError::NoDelays("FEE"))?;
118 trace!("Attempting to use delays:");
119 match &dipole_delays {
120 Delays::Full(d) => {
121 let mut last_row = None;
122 for (i, row) in d.outer_iter().enumerate() {
123 if let Some(last_row) = last_row {
124 if row == last_row {
125 continue;
126 }
127 }
128 trace!("{i:03} {row}");
129 last_row = Some(row);
130 }
131 }
132 Delays::Partial(d) => trace!("{d:?}"),
133 }
134
135 match &dipole_delays {
137 Delays::Partial(v) => {
138 if v.len() != 16 || v.iter().any(|&v| v > 32) {
139 return Err(BeamError::BadDelays);
140 }
141 }
142
143 Delays::Full(a) => {
144 if a.len_of(Axis(1)) != 16 || a.iter().any(|&v| v > 32) {
145 return Err(BeamError::BadDelays);
146 }
147 if a.len_of(Axis(0)) != total_num_tiles {
148 return Err(BeamError::InconsistentDelays {
149 num_rows: a.len_of(Axis(0)),
150 num_tiles: total_num_tiles,
151 });
152 }
153 }
154 }
155
156 let dipole_gains = if unity_dipole_gains {
157 printer.push_line("Assuming all dipoles are \"alive\"".into());
158 None
159 } else {
160 if dipole_gains.is_none() {
163 match input_data_type {
164 Some(VisInputType::MeasurementSet) => [
165 "Measurement sets cannot supply dead dipole information.".into(),
166 "Without a metafits file, we must assume all dipoles are alive.".into(),
167 "This will make beam Jones matrices inaccurate in sky-model generation."
168 .into(),
169 ]
170 .warn(),
171 Some(VisInputType::Uvfits) => [
172 "uvfits files cannot supply dead dipole information.".into(),
173 "Without a metafits file, we must assume all dipoles are alive.".into(),
174 "This will make beam Jones matrices inaccurate in sky-model generation."
175 .into(),
176 ]
177 .warn(),
178 Some(VisInputType::Raw) => {
179 unreachable!("Raw data inputs always specify dipole gains")
180 }
181 None => (),
182 }
183 }
184 dipole_gains
185 };
186 if let Some(dipole_gains) = dipole_gains.as_ref() {
187 trace!("Attempting to use dipole gains:");
188 let mut last_row = None;
189 for (i, row) in dipole_gains.outer_iter().enumerate() {
190 if let Some(last_row) = last_row {
191 if row == last_row {
192 continue;
193 }
194 }
195 trace!("{i:03} {row}");
196 last_row = Some(row);
197 }
198
199 if dipole_gains.iter().any(|&g| g != 0.0 && g != 1.0) {
202 printer.push_line(
203 "Using Aman's 'DipAmps' dipole gains from the metafits".into(),
204 );
205 } else {
206 let num_tiles_with_dead_dipoles = dipole_gains
207 .outer_iter()
208 .filter(|tile_dipole_gains| {
209 tile_dipole_gains.iter().any(|g| g.abs() < f64::EPSILON)
210 })
211 .count();
212 printer.push_line(
213 format!(
214 "Using dead dipole information ({num_tiles_with_dead_dipoles} tiles affected)"
215 )
216 .into(),
217 );
218 }
219 } else {
220 dipole_delays.set_to_ideal_delays();
225 let ideal_delays = dipole_delays.get_ideal_delays();
226
227 if unity_dipole_gains && ideal_delays.contains(&32) {
230 "Some ideal dipole delays are 32; these dipoles will not have unity gains"
231 .warn()
232 }
233 }
234
235 let beam = if let Some(bf) = beam_file {
236 FEEBeam::new(&bf, total_num_tiles, dipole_delays, dipole_gains)?
238 } else {
239 FEEBeam::new_from_env(total_num_tiles, dipole_delays, dipole_gains)?
242 };
243 Box::new(beam)
244 }
245 };
246
247 if let Some(d) = beam.get_ideal_dipole_delays() {
248 printer.push_block(vec![
249 format!(
250 "Ideal dipole delays: [{:>2} {:>2} {:>2} {:>2}",
251 d[0], d[1], d[2], d[3]
252 )
253 .into(),
254 format!(
255 " {:>2} {:>2} {:>2} {:>2}",
256 d[4], d[5], d[6], d[7]
257 )
258 .into(),
259 format!(
260 " {:>2} {:>2} {:>2} {:>2}",
261 d[8], d[9], d[10], d[11]
262 )
263 .into(),
264 format!(
265 " {:>2} {:>2} {:>2} {:>2}]",
266 d[12], d[13], d[14], d[15]
267 )
268 .into(),
269 ]);
270 }
271
272 if let Some(f) = beam.get_beam_file() {
273 printer.push_line(format!("File: {}", f.display()).into());
274 }
275
276 printer.display();
277 Ok(beam)
278 }
279}