pueoAnalysisTools
Loading...
Searching...
No Matches
antenna_pairs.py
Go to the documentation of this file.
1
14
15import numpy as np
16import polars as pl
17
18
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.
22 @ingroup AAA
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.
26
27* Parameters:
28 * `antennas`: The `AntIdx` column is required, all other columns are optional.
29 See the following examples.
30 -# A dataframe with
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)
34
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$
39
40* Example input `antennas`:
41 ```
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 └────────┴──────┴───────────┴────────┴───────┴────────┴───────┴────────────┴──────────┴───────────┘
53 ```
54* With default @p phi_sector_group_size and the example input above, the output schema looks like
55 ```
56 Rows: 912
57 Columns: 20
58 $ A1_AntNum <enum>
59 $ A1_Ring <u8>
60 $ A1_PhiSector <u8>
61 $ A1_AntIdx <u32>
62 $ A1_X[m] <f64>
63 $ A1_Y[m] <f64>
64 $ A1_Z[m] <f64>
65 $ A1_Pitch[deg] <f64>
66 $ A1_Yaw[deg] <f64>
67 $ A1_Roll[deg] <f64>
68 $ A2_AntNum <enum>
69 $ A2_Ring <u8>
70 $ A2_PhiSector <u8>
71 $ A2_AntIdx <u32>
72 $ A2_X[m] <f64>
73 $ A2_Y[m] <f64>
74 $ A2_Z[m] <f64>
75 $ A2_Pitch[deg] <f64>
76 $ A2_Yaw[deg] <f64>
77 $ A2_Roll[deg] <f64>
78 ```
79 """
80
81 from antenna_attributes import NUM_PHI_SECTORS
82 from scipy.sparse import diags_array
83
84 # will abort early if column not present
85 antennas["AntIdx"]
86
87 building_block = np.ones((4, 4), dtype=int) # ie. four antennas in one phi sector
88 if phi_sector_group_size < 1:
89 raise ValueError("Group size has to be greater than 0!")
90
91 dia_idx = np.unique(np.arange(-phi_sector_group_size + 1, phi_sector_group_size) % NUM_PHI_SECTORS)
92
93 phi_neighbors = diags_array(np.ones(len(dia_idx)), offsets=dia_idx, dtype=int,
94 shape=(NUM_PHI_SECTORS, NUM_PHI_SECTORS)).toarray()
95 # __make_plot_for_sanity_check(phi_neighbors, "Phi Sector Groups")
96
97 antenna_pair_matrix = np.triu(np.kron(phi_neighbors, building_block), 1)
98 # __make_plot_for_sanity_check(antenna_pair_matrix, "Antenna Pairs")
99
100 antenna_pairs = (
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)
105 .select(
106 pl.col(r"^A1_.*$"),
107 pl.all().exclude(r"^A1_.*$", '2').name.prefix("A2_")
108 )
109 )
110
111 return antenna_pairs
112
113
114def __make_plot_for_sanity_check(square_matrix, titlename):
115
116 import matplotlib.pyplot as plt
117 plt.rcParams["figure.figsize"] = (15, 15)
118 plt.rcParams["font.size"] = 9
119
120 assert (np.shape(square_matrix)[0] == np.shape(square_matrix)[1]), "Not a square matrix."
121
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)
133 plt.title(titlename)
134 plt.savefig(f"{titlename}.svg")
135
136
137# main function, serving as an example
138if __name__ == "__main__":
139 import os
140 from pathlib import Path
141 from antenna_attributes import read_MI_antenna_geometry
142
143 uncalibrated_antenna_positions: Path = (
144 os.environ.get("PUEO_UTIL_INSTALL_DIR") / Path("share/pueo/geometry/jun25/qrh.dat")
145 )
146 antennas: pl.DataFrame = read_MI_antenna_geometry(uncalibrated_antenna_positions)
147
148 try:
149 bad = antennas.select(pl.all().exclude("AntIdx"))
150 generate_MI_antenna_pairs(antennas=bad)
151
152 except pl.exceptions.ColumnNotFoundError:
153 print("Column AntIdx is required, so this should fail!")
154
155 print("On the other hand, keeping only AntIdx is okay:")
156 good = antennas.select("AntIdx")
157 print(generate_MI_antenna_pairs(antennas=good))