19def generate_MI_antenna_pairs(antennas: pl.DataFrame,
20 phi_sector_group_size: int = 3) -> pl.DataFrame:
21 r"""! Pairs up neighboring antennas in the main instrument.
23 @param[in] antennas Column `AntIdx` is **required**
24 @param[in] phi_sector_group_size (optional) Make sure \f$\geq 1\f$
25 @retval antenna_pairs See the sample output below.
28 * `antennas`: The `AntIdx` column is required, all other columns are optional.
29 See the following examples.
31 [placeholder integers](#calibration.generate_antenna_phase_center_pairs_with_placeholders())
32 as antenna phase center positions, or
33 -# Directly from [qrh.dat](#antenna_attributes.read_MI_antenna_geometry)
35 * `phi_sector_group_size`: Actually this is (roughly) _half_ the group size.
36 Check out the [plots](@ref ant_pair_img) in the file description
37 to see what effect this parameter has on the number of pairs generated.
38 @warning For non-default values, make sure @p phi_sector_group_size \f$\geq 1\f$
40* Example input `antennas`:
42 ┌────────┬──────┬───────────┬────────┬───────┬────────┬───────┬────────────┬──────────┬───────────┐
43 │ AntNum ┆ Ring ┆ PhiSector ┆ AntIdx ┆ X[m] ┆ Y[m] ┆ Z[m] ┆ Pitch[deg] ┆ Yaw[deg] ┆ Roll[deg] │
44 │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
45 │ enum ┆ u8 ┆ u8 ┆ u32 ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
46 ╞════════╪══════╪═══════════╪════════╪═══════╪════════╪═══════╪════════════╪══════════╪═══════════╡
47 │ 101 ┆ 1 ┆ 1 ┆ 0 ┆ 1.488 ┆ 0.0 ┆ 5.114 ┆ -10.0 ┆ 0.0 ┆ 0.0 │
48 │ 201 ┆ 2 ┆ 1 ┆ 1 ┆ 2.596 ┆ 0.0 ┆ 1.457 ┆ -10.0 ┆ 0.0 ┆ 0.0 │
49 │ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │
50 │ 324 ┆ 3 ┆ 24 ┆ 94 ┆ 2.507 ┆ -0.672 ┆ 0.728 ┆ -10.0 ┆ -15.0 ┆ 0.0 │
51 │ 424 ┆ 4 ┆ 24 ┆ 95 ┆ 2.507 ┆ -0.672 ┆ 0.0 ┆ -10.0 ┆ -15.0 ┆ 0.0 │
52 └────────┴──────┴───────────┴────────┴───────┴────────┴───────┴────────────┴──────────┴───────────┘
54* With default @p phi_sector_group_size and the example input above, the output schema looks like
81 from antenna_attributes
import NUM_PHI_SECTORS
82 from scipy.sparse
import diags_array
87 building_block = np.ones((4, 4), dtype=int)
88 if phi_sector_group_size < 1:
89 raise ValueError(
"Group size has to be greater than 0!")
91 dia_idx = np.unique(np.arange(-phi_sector_group_size + 1, phi_sector_group_size) % NUM_PHI_SECTORS)
93 phi_neighbors = diags_array(np.ones(len(dia_idx)), offsets=dia_idx, dtype=int,
94 shape=(NUM_PHI_SECTORS, NUM_PHI_SECTORS)).toarray()
97 antenna_pair_matrix = np.triu(np.kron(phi_neighbors, building_block), 1)
101 pl.DataFrame(np.where(antenna_pair_matrix > 0), schema=[
'1',
'2'])
102 .join(antennas, right_on=
"AntIdx", left_on=
'1', coalesce=
False)
103 .select(pl.all().exclude(
'1',
'2').name.prefix(
"A1_"),
'2')
104 .join(antennas, right_on=
"AntIdx", left_on=
'2', coalesce=
False)
107 pl.all().exclude(
r"^A1_.*$",
'2').name.prefix(
"A2_")
114def __make_plot_for_sanity_check(square_matrix, titlename):
116 import matplotlib.pyplot
as plt
117 plt.rcParams[
"figure.figsize"] = (15, 15)
118 plt.rcParams[
"font.size"] = 9
120 assert (np.shape(square_matrix)[0] == np.shape(square_matrix)[1]),
"Not a square matrix."
122 square_matrix_side_length = np.shape(square_matrix)[0]
123 minor = np.arange(.5, square_matrix_side_length)
124 plt.xticks(np.arange(square_matrix_side_length),
125 np.arange(square_matrix_side_length) + 1, rotation=90)
126 plt.xticks(minor, minor=
True)
127 plt.yticks(np.arange(square_matrix_side_length), np.arange(square_matrix_side_length) + 1)
128 plt.yticks(minor, minor=
True)
129 plt.grid(which=
'minor', color=
'k', alpha=.3,)
130 plt.tick_params(axis=
'both', which=
'both', top=
True, right=
True, bottom=
True, left=
True,
131 labeltop=
True, labelright=
True)
132 plt.imshow(square_matrix, cmap=
'Greys', alpha=.6)
134 plt.savefig(f
"{titlename}.svg")
138if __name__ ==
"__main__":
140 from pathlib
import Path
141 from antenna_attributes
import read_MI_antenna_geometry
143 uncalibrated_antenna_positions: Path = (
144 os.environ.get(
"PUEO_UTIL_INSTALL_DIR") / Path(
"share/pueo/geometry/jun25/qrh.dat")
146 antennas: pl.DataFrame = read_MI_antenna_geometry(uncalibrated_antenna_positions)
149 bad = antennas.select(pl.all().exclude(
"AntIdx"))
150 generate_MI_antenna_pairs(antennas=bad)
152 except pl.exceptions.ColumnNotFoundError:
153 print(
"Column AntIdx is required, so this should fail!")
155 print(
"On the other hand, keeping only AntIdx is okay:")
156 good = antennas.select(
"AntIdx")
157 print(generate_MI_antenna_pairs(antennas=good))