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_LF_antenna_pairs(antennas: pl.DataFrame) -> pl.DataFrame:
20
21 # make antenna_pair_matrix
22 antenna_pair_matrix = np.triu(np.ones((8, 8), dtype=int), 1)
23
24 antidx = antennas["AntIdx"].to_numpy()
25 i, j = np.where(antenna_pair_matrix > 0)
26 pair_df = pl.DataFrame({"1": antidx[i], "2": antidx[j],})
27
28
29 # __make_plot_for_sanity_check(antenna_pair_matrix, "LF Antenna Pairs")
30
31 antenna_pairs = (
32 pair_df
33 .join(antennas, right_on="AntIdx", left_on='1', coalesce=False)
34 .select(pl.all().exclude('1', '2').name.prefix("A1_"), '2')
35 .join(antennas, right_on="AntIdx", left_on='2', coalesce=False)
36 .select(
37 pl.col(r"^A1_.*$"),
38 pl.all().exclude(r"^A1_.*$", '2').name.prefix("A2_")
39 )
40 )
41
42 return antenna_pairs
43
44def generate_MI_antenna_pairs(antennas: pl.DataFrame,
45 phi_sector_group_size: int = 3) -> pl.DataFrame:
46 r"""! Pairs up neighboring antennas in the main instrument.
47 @ingroup AAA
48 @param[in] antennas Column `AntIdx` is **required**
49 @param[in] phi_sector_group_size (optional) Make sure \f$\geq 1\f$
50 @retval antenna_pairs See the sample output below.
51
52* Parameters:
53 * `antennas`: The `AntIdx` column is required, all other columns are optional.
54 See the following examples.
55 -# A dataframe with
56 [placeholder integers](#calibration.generate_antenna_phase_center_pairs_with_placeholders())
57 as antenna phase center positions, or
58 -# Directly from [qrh.dat](#antenna_attributes.read_MI_antenna_geometry)
59
60 * `phi_sector_group_size`: Actually this is (roughly) _half_ the group size.
61 Check out the [plots](@ref ant_pair_img) in the file description
62 to see what effect this parameter has on the number of pairs generated.
63 @warning For non-default values, make sure @p phi_sector_group_size \f$\geq 1\f$
64
65* Example input `antennas`:
66 ```
67 ┌────────┬──────┬───────────┬────────┬───────┬────────┬───────┬────────────┬──────────┬───────────┐
68 │ AntNum ┆ Ring ┆ PhiSector ┆ AntIdx ┆ X[m] ┆ Y[m] ┆ Z[m] ┆ Pitch[deg] ┆ Yaw[deg] ┆ Roll[deg] │
69 │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
70 │ enum ┆ u8 ┆ u8 ┆ u32 ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
71 ╞════════╪══════╪═══════════╪════════╪═══════╪════════╪═══════╪════════════╪══════════╪═══════════╡
72 │ 101 ┆ 1 ┆ 1 ┆ 0 ┆ 1.488 ┆ 0.0 ┆ 5.114 ┆ -10.0 ┆ 0.0 ┆ 0.0 │
73 │ 201 ┆ 2 ┆ 1 ┆ 1 ┆ 2.596 ┆ 0.0 ┆ 1.457 ┆ -10.0 ┆ 0.0 ┆ 0.0 │
74 │ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │
75 │ 324 ┆ 3 ┆ 24 ┆ 94 ┆ 2.507 ┆ -0.672 ┆ 0.728 ┆ -10.0 ┆ -15.0 ┆ 0.0 │
76 │ 424 ┆ 4 ┆ 24 ┆ 95 ┆ 2.507 ┆ -0.672 ┆ 0.0 ┆ -10.0 ┆ -15.0 ┆ 0.0 │
77 └────────┴──────┴───────────┴────────┴───────┴────────┴───────┴────────────┴──────────┴───────────┘
78 ```
79* With default @p phi_sector_group_size and the example input above, the output schema looks like
80 ```
81 Rows: 912
82 Columns: 20
83 $ A1_AntNum <enum>
84 $ A1_Ring <u8>
85 $ A1_PhiSector <u8>
86 $ A1_AntIdx <u32>
87 $ A1_X[m] <f64>
88 $ A1_Y[m] <f64>
89 $ A1_Z[m] <f64>
90 $ A1_Pitch[deg] <f64>
91 $ A1_Yaw[deg] <f64>
92 $ A1_Roll[deg] <f64>
93 $ A2_AntNum <enum>
94 $ A2_Ring <u8>
95 $ A2_PhiSector <u8>
96 $ A2_AntIdx <u32>
97 $ A2_X[m] <f64>
98 $ A2_Y[m] <f64>
99 $ A2_Z[m] <f64>
100 $ A2_Pitch[deg] <f64>
101 $ A2_Yaw[deg] <f64>
102 $ A2_Roll[deg] <f64>
103 ```
104 """
105
106 from antenna_attributes import NUM_PHI_SECTORS
107 from scipy.sparse import diags_array
108
109 # will abort early if column not present
110 antennas["AntIdx"]
111
112 building_block = np.ones((4, 4), dtype=int) # ie. four antennas in one phi sector
113 if phi_sector_group_size < 1:
114 raise ValueError("Group size has to be greater than 0!")
115
116 dia_idx = np.unique(np.arange(-phi_sector_group_size + 1, phi_sector_group_size) % NUM_PHI_SECTORS)
117
118 phi_neighbors = diags_array(np.ones(len(dia_idx)), offsets=dia_idx, dtype=int,
119 shape=(NUM_PHI_SECTORS, NUM_PHI_SECTORS)).toarray()
120 # __make_plot_for_sanity_check(phi_neighbors, "Phi Sector Groups")
121
122 antenna_pair_matrix = np.triu(np.kron(phi_neighbors, building_block), 1)
123
124 # __make_plot_for_sanity_check(antenna_pair_matrix, "Antenna Pairs")
125
126 antenna_pairs = (
127 pl.DataFrame(np.where(antenna_pair_matrix > 0), schema=['1', '2'])
128 .join(antennas, right_on="AntIdx", left_on='1', coalesce=False)
129 .select(pl.all().exclude('1', '2').name.prefix("A1_"), '2')
130 .join(antennas, right_on="AntIdx", left_on='2', coalesce=False)
131 .select(
132 pl.col(r"^A1_.*$"),
133 pl.all().exclude(r"^A1_.*$", '2').name.prefix("A2_")
134 )
135 )
136
137 return antenna_pairs
138
139
140def __make_plot_for_sanity_check(square_matrix, titlename):
141
142 import matplotlib.pyplot as plt
143 plt.rcParams["figure.figsize"] = (15, 15)
144 plt.rcParams["font.size"] = 9
145
146 assert (np.shape(square_matrix)[0] == np.shape(square_matrix)[1]), "Not a square matrix."
147
148 square_matrix_side_length = np.shape(square_matrix)[0]
149 minor = np.arange(.5, square_matrix_side_length)
150 plt.xticks(np.arange(square_matrix_side_length),
151 np.arange(square_matrix_side_length) + 1, rotation=90)
152 plt.xticks(minor, minor=True)
153 plt.yticks(np.arange(square_matrix_side_length), np.arange(square_matrix_side_length) + 1)
154 plt.yticks(minor, minor=True)
155 plt.grid(which='minor', color='k', alpha=.3,)
156 plt.tick_params(axis='both', which='both', top=True, right=True, bottom=True, left=True,
157 labeltop=True, labelright=True)
158 plt.imshow(square_matrix, cmap='Greys', alpha=.6)
159 plt.title(titlename)
160 plt.savefig(f"{titlename}.svg")
161
162
163# main function, serving as an example
164if __name__ == "__main__":
165 import os
166 from pathlib import Path
167 from antenna_attributes import read_antenna_geometry
168
169 uncalibrated_antenna_positions: Path = (
170 os.environ.get("PUEO_UTIL_INSTALL_DIR") / Path("share/pueo/geometry/aug25/qrh.dat") # qrh.dat for MI or lf.dat for LF
171 )
172
173 antennas: pl.DataFrame = read_antenna_geometry(uncalibrated_antenna_positions)
174 #antennas: pl.DataFrame = read_antenna_geometry(uncalibrated_antenna_positions, ant_type="LF")
175
176 try:
177 bad = antennas.select(pl.all().exclude("AntIdx"))
178 generate_LF_antenna_pairs(antennas=bad)
179 except pl.exceptions.ColumnNotFoundError:
180 print("Column AntIdx is required, so this should fail!")
181
182 print("On the other hand, keeping only AntIdx is okay:")
183 good = antennas.select("AntIdx")
184 print(generate_MI_antenna_pairs(antennas=good))
185 #print(generate_LF_antenna_pairs(antennas))