3@brief `main()` shows how to use
4[make_time_delay_skymap()](#time_delays.make_time_delay_skymap)
6* Running the `main` function through `python3 time_delays.py` will print out a DataFrame
7 containing one pair of antennas (`Ant 203` and `Ant 104`)
9* The dataframe will also have a column `time delays [sec]`, which is the "time delay skymap"
10 (for this pair of antennas).
12* [_plot_one_random_row_as_example()](#time_delays._plot_one_random_row_as_example) will plot the
13 time delay skymap for a randomly selected pair of antennas.
14 * \f$\phi\f$ denotes the azimuthal angle of the incoming signal's direction, and
15 * \f$\theta\f$ the zenith angle (in payload coordinates).
16 * time (the colorbar) in seconds.
18
30def _make_grid(sparse=False) -> tuple[np.ndarray, np.ndarray]:
33 @brief Creates a 2D grid \f$(\phi, \theta)\f$ of azimuthal and zenith angles.
34 @param[in] sparse see [NumPy's documentation](https://numpy.org/doc/2.2/reference/generated/numpy.meshgrid.html#numpy-meshgrid)
35 @retval phi azimuthal angle coordinates
36 @retval theta zenith angle coordinates
38 * This grid represents the "bottom hemisphere":
39 * Azimuthal angle range:\f$0 < \phi < 2 \pi\f$
40 * **Zenith** (not elevation!) angle range: \f$ \pi/2 < \theta < \pi\f$
41 * The figure below shows a sparse grid as an example.
42 @note The real grid currently in use is 180-by-360
43 (i.e. 360 points for \f$\phi\f$, 180 points for \f$\theta\f$)
45 
47 azimuthal_angle_mesh = np.linspace(0, 2 * np.pi, 360)
48 zenith_angle_mesh = np.linspace(np.pi / 2, np.pi, 180)
49 phi, theta = np.meshgrid(azimuthal_angle_mesh, zenith_angle_mesh, sparse=sparse)
54def _make_unit_vectors_on_bottom_hemisphere() -> tuple[np.ndarray, np.ndarray, np.ndarray]:
57 @brief Returns unit vectors pointing to the origin from the surface of a unit bottom hemisphere.
62 * Calls #_make_grid() to generate a 2D grid of \f$\phi\f$s and \f$\theta\f$s.
63 * Converts each point on the grid into Cartesian coordinates; assuming `r=1`:
64 * \f$ x = \sin\theta \cdot \cos\phi \f$
65 * \f$ y = \sin\theta \cdot \sin\phi \f$
66 * \f$ z = \cos\theta \f$
67 * Each point is a unit vector pointing from the origin to somewhere on the hemisphere.
68 * Thus, the unit vector \f$\hat{\bf{u}}\equiv(-x,-y,-z)\f$ represents a possible (incoming) signal
69 direction, assuming that the payload is at the center of the sphere.
70 * The unit bottom hemisphere:
72 incoming_x, incoming_y, incoming_z = _make_unit_vectors_on_bottom_hemisphere()
74 ax = fig.add_subplot(projection='3d')
75 ax.plot_surface(-incoming_x, -incoming_y, -incoming_z)
76 ax.set_aspect('equal')
79 
81 phi, theta = _make_grid()
82 incoming_x = -np.sin(theta) * np.cos(phi)
83 incoming_y = -np.sin(theta) * np.sin(phi)
84 incoming_z = -np.cos(theta)
86 return incoming_x, incoming_y, incoming_z
89def make_time_delay_skymap(antenna_pairs: pl.DataFrame) -> pl.DataFrame:
90 r"""! Computes the expected incoming-signal time delays between
91 [antenna pairs](#antenna_pairs.generate_MI_antenna_pairs)
92 based on pairwise displacement vectors and signal directions.
94 @param[in] antenna_pairs The output of #antenna_pairs.generate_MI_antenna_pairs
95 @retval time_delays See sample output below
97* The following columns are **required** in `antenna_pairs`
105* Sample schema of `time_delays`, assuming only the essential columns have been supplied in `antenna_pairs`
113$ time delays [sec] <array[f64, (180, 360)]>
116 -# The time delay between any antenna pair is computed via
117 \f$ (\bf{X_1} - \bf{X_2}) \cdot \hat{\bf{u}} \f$ / \f$c\f$
118 * \f$\bf{X}\f$ denotes the antenna position,
119 * \f$\hat{\bf{u}}\f$ is a unit vector that represents the
120 [signal direction](#_make_unit_vectors_on_bottom_hemisphere), and
121 * \f$c\f$ is the speed of light.
123 -# Each entry in the column `time delays [sec]` would be a 2D matrix with the same dimensions
124 as the 2D grid generated by #_make_grid; let's call them **time delay skymaps**.
125 -# Each point on a time delay skymap represents the expected time-delay based on where the
126 signal is coming from and where the antennas are on the payload.
127 -# Time delay occurs since the signal has to travel an extra distance to reach the second antenna,
128 after it hits the first antenna:
129 \anchor timedelaybydotproduct
130 \image html time_delay_by_dot_product.png width=50%
132 -# As an example, the figure below shows the expected time delays (in seconds) between antenna
135 
137 from scipy
import constants
138 incoming_x, incoming_y, incoming_z = _make_unit_vectors_on_bottom_hemisphere()
144 pl.concat_arr(
"A1_X[m]",
"A1_Y[m]",
"A1_Z[m]").alias(
"A1_position[m]"),
145 pl.concat_arr(
"A2_X[m]",
"A2_Y[m]",
"A2_Z[m]").alias(
"A2_position[m]")
148 (pl.col(
"A1_position[m]") - pl.col(
"A2_position[m]")).map_batches(
149 lambda displacement_vector: np.array([
150 (dx * incoming_x + dy * incoming_y + dz * incoming_z) / constants.c
151 for (dx, dy, dz)
in displacement_vector
153 ).alias(
"time delays [sec]")
155 .select(pl.all().exclude(
r"^A[12]_position\[m\]$"))
161def _plot_one_random_row_as_example(one_row: pl.DataFrame, plot_name: str) ->
None:
162 """!Makes an example plot"""
163 import matplotlib.pyplot
as plt
164 plt.rcParams.update({
"font.size": 16})
166 pl.Config.set_tbl_cols(one_row.width)
168 phi, theta = _make_grid()
169 plt.contourf(phi, theta, one_row[
"time delays [sec]"][0])
171 plt.xlabel(
r"$\phi$")
172 plt.ylabel(
r"$\theta$")
173 plt.xticks([0, np.pi, 2 * np.pi], labels=[
"0",
r"$\pi$",
r"$2\pi$"])
174 plt.yticks([np.pi / 2, np.pi], labels=[
r"$\pi / 2$",
r"$\pi$"])
175 plt.colorbar(label=
"time delay [sec]")
176 plt.savefig(plot_name)
179if __name__ ==
"__main__":
182 from pathlib
import Path
183 from antenna_attributes
import read_MI_antenna_geometry
184 from antenna_pairs
import generate_MI_antenna_pairs
186 uncalibrated_antenna_positions: Path = (
187 os.environ.get(
"PUEO_UTIL_INSTALL_DIR") / Path(
"share/pueo/geometry/jun25/qrh.dat")
189 antennas: pl.DataFrame = read_MI_antenna_geometry(uncalibrated_antenna_positions)
191 antenna_pairs = generate_MI_antenna_pairs(antennas)
194 bad = antenna_pairs.select(pl.all().exclude(
r"^A[12]_[XYZ]\[m\]$"))
195 time_delay = make_time_delay_skymap(bad)
196 except pl.exceptions.ColumnNotFoundError:
197 print(
"The above should fail due to missing columns!")
199 print(
"But below is okay and the output is:")
200 okay = antenna_pairs.select(
r"^A[12]_[XYZ]\[m\]$")
201 time_delay = make_time_delay_skymap(okay)
203 _plot_one_random_row_as_example(time_delay.sample(1), plot_name=
"foobar")