tabsim 0.0.2__tar.gz → 0.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. {tabsim-0.0.2 → tabsim-0.0.3}/PKG-INFO +10 -2
  2. {tabsim-0.0.2 → tabsim-0.0.3}/README.md +5 -0
  3. tabsim-0.0.3/docs/conf.py +14 -0
  4. {tabsim-0.0.2 → tabsim-0.0.3}/pyproject.toml +3 -2
  5. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/config.py +11 -9
  6. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/jax/coordinates.py +255 -1
  7. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/scripts/sim_vis.py +3 -1
  8. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/sky.py +3 -1
  9. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/tle.py +6 -4
  10. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/write.py +39 -3
  11. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim.egg-info/PKG-INFO +10 -2
  12. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim.egg-info/SOURCES.txt +1 -0
  13. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim.egg-info/requires.txt +4 -0
  14. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim.egg-info/top_level.txt +1 -0
  15. {tabsim-0.0.2 → tabsim-0.0.3}/LICENSE +0 -0
  16. {tabsim-0.0.2 → tabsim-0.0.3}/setup.cfg +0 -0
  17. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/__init__.py +0 -0
  18. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/dask/__init__.py +0 -0
  19. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/dask/coordinates.py +0 -0
  20. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/dask/extras.py +0 -0
  21. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/dask/interferometry.py +0 -0
  22. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/dask/observation.py +0 -0
  23. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/data/EDA2.itrf.txt +0 -0
  24. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/data/MWA.itrf.txt +0 -0
  25. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/data/MeerKAT.itrf.txt +0 -0
  26. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/data/__init__.py +0 -0
  27. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/data/aux_data/calibrator.skymodel +0 -0
  28. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/data/aux_data/norad_satellite.rfimodel +0 -0
  29. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/data/aux_data/satellite.ole +0 -0
  30. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/data/aux_data/satellite.rfimodel +0 -0
  31. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/data/aux_data/stationary.loc +0 -0
  32. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/data/aux_data/stationary.rfimodel +0 -0
  33. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/data/aux_data/tles/2023-02-21-J8HE0T.json +0 -0
  34. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/data/config_files/sim_config_base.yaml +0 -0
  35. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/data/config_files/tab_config_base.yaml +0 -0
  36. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/jax/__init__.py +0 -0
  37. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/jax/interferometry.py +0 -0
  38. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/jax/utils.py +0 -0
  39. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/plot.py +0 -0
  40. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/scripts/__init__.py +0 -0
  41. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/scripts/ast_region.py +0 -0
  42. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/scripts/flag_data.py +0 -0
  43. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/scripts/tle_sat_region.py +0 -0
  44. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim/tools.py +0 -0
  45. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim.egg-info/dependency_links.txt +0 -0
  46. {tabsim-0.0.2 → tabsim-0.0.3}/tabsim.egg-info/entry_points.txt +0 -0
  47. {tabsim-0.0.2 → tabsim-0.0.3}/tests/test_basic.py +0 -0
  48. {tabsim-0.0.2 → tabsim-0.0.3}/tests/test_sim-vis.py +0 -0
  49. {tabsim-0.0.2 → tabsim-0.0.3}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tabsim
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: Trajectory-based Radio Frequency Interference (RFI) simulations for radio interferometeric data.
5
5
  Author-email: Chris Finlay <christopher.finlay@epfl.ch>
6
6
  Maintainer-email: Chris Finlay <christopher.finlay@epfl.ch>
@@ -680,7 +680,7 @@ License: GNU GENERAL PUBLIC LICENSE
680
680
  <https://www.gnu.org/licenses/why-not-lgpl.html>.
681
681
 
682
682
  Project-URL: Repository, https://github.com/chrisfinlay/tab-sim.git
683
- Requires-Python: <3.13,>=3.10
683
+ Requires-Python: <3.13,>=3.9
684
684
  Description-Content-Type: text/markdown
685
685
  License-File: LICENSE
686
686
  Requires-Dist: jax
@@ -704,12 +704,16 @@ Requires-Dist: ipython; extra == "ipy"
704
704
  Requires-Dist: ipykernel; extra == "ipy"
705
705
  Provides-Extra: test
706
706
  Requires-Dist: pytest; extra == "test"
707
+ Provides-Extra: docs
708
+ Requires-Dist: sphinx; extra == "docs"
709
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
707
710
  Dynamic: license-file
708
711
 
709
712
  # tab-sim
710
713
 
711
714
  [![DOI:10.1093/mnras/stad1979](https://zenodo.org/badge/DOI/10.1093/mnras/stad1979.svg)](https://doi.org/10.1093/mnras/stad1979)
712
715
  [![DOI:10.48550/arXiv.2502.00106](https://img.shields.io/badge/arXiv-2502.00106-b31b1b.svg)](https://doi.org/10.48550/arXiv.2502.00106)
716
+ [![Documentation Status](https://readthedocs.org/projects/tab-sim/badge/?version=latest)](https://tab-sim.readthedocs.io/en/latest/?badge=latest)
713
717
 
714
718
  **T**r**A**jectory **B**ased RFI **Sim**ulations of radio interferometry data.
715
719
  A source to visibility model for RFI sources including certain near-field effects.
@@ -717,6 +721,10 @@ A source to visibility model for RFI sources including certain near-field effect
717
721
  `tab-sim` is written in [JAX](https://jax.readthedocs.io/en/latest/notebooks/quickstart.html)
718
722
  and [Dask](https://www.dask.org) and can therefore use GPUs and/or CPUs to perform larger than memory computation.
719
723
 
724
+ ## Documentation
725
+
726
+ [https://tab-sim.readthedocs.io/](https://tab-sim.readthedocs.io/)
727
+
720
728
  ## Installation
721
729
 
722
730
  The following instructions are expected to work on Linux machine. If you are running Windows it is recommended to use WSL. If you are running Mac then it is best to start with a [conda](https://docs.conda.io/en/latest/)/[mamba](https://mamba.readthedocs.io/en/latest/index.html) environment and install `python-casacore` before continuing with the `pip` install method. If all else fails there is the [Docker install](#docker).
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![DOI:10.1093/mnras/stad1979](https://zenodo.org/badge/DOI/10.1093/mnras/stad1979.svg)](https://doi.org/10.1093/mnras/stad1979)
4
4
  [![DOI:10.48550/arXiv.2502.00106](https://img.shields.io/badge/arXiv-2502.00106-b31b1b.svg)](https://doi.org/10.48550/arXiv.2502.00106)
5
+ [![Documentation Status](https://readthedocs.org/projects/tab-sim/badge/?version=latest)](https://tab-sim.readthedocs.io/en/latest/?badge=latest)
5
6
 
6
7
  **T**r**A**jectory **B**ased RFI **Sim**ulations of radio interferometry data.
7
8
  A source to visibility model for RFI sources including certain near-field effects.
@@ -9,6 +10,10 @@ A source to visibility model for RFI sources including certain near-field effect
9
10
  `tab-sim` is written in [JAX](https://jax.readthedocs.io/en/latest/notebooks/quickstart.html)
10
11
  and [Dask](https://www.dask.org) and can therefore use GPUs and/or CPUs to perform larger than memory computation.
11
12
 
13
+ ## Documentation
14
+
15
+ [https://tab-sim.readthedocs.io/](https://tab-sim.readthedocs.io/)
16
+
12
17
  ## Installation
13
18
 
14
19
  The following instructions are expected to work on Linux machine. If you are running Windows it is recommended to use WSL. If you are running Mac then it is best to start with a [conda](https://docs.conda.io/en/latest/)/[mamba](https://mamba.readthedocs.io/en/latest/index.html) environment and install `python-casacore` before continuing with the `pip` install method. If all else fails there is the [Docker install](#docker).
@@ -0,0 +1,14 @@
1
+ import os
2
+ import sys
3
+ sys.path.insert(0, os.path.abspath('..'))
4
+
5
+ project = 'tab‑sim'
6
+ author = 'Chris Finlay'
7
+ extensions = [
8
+ 'sphinx.ext.autodoc',
9
+ 'sphinx.ext.napoleon',
10
+ 'sphinx.ext.viewcode',
11
+ ]
12
+ templates_path = ['_templates']
13
+ exclude_patterns = []
14
+ html_theme = 'sphinx_rtd_theme'
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "tabsim"
8
- version = "0.0.2"
8
+ version = "0.0.3"
9
9
  license = {file = "LICENSE"}
10
10
  readme = "README.md"
11
11
  # dynamic = ["version", "readme"]
@@ -17,7 +17,7 @@ maintainers = [
17
17
  ]
18
18
  description = "Trajectory-based Radio Frequency Interference (RFI) simulations for radio interferometeric data."
19
19
 
20
- requires-python = ">=3.10,<3.13"
20
+ requires-python = ">=3.9,<3.13"
21
21
  dependencies = [
22
22
  "jax", "dask<=2024.10.0", "xarray", "zarr<3.0.0",
23
23
  "dask-ms>=0.2.21", "scipy", "tqdm", "matplotlib",
@@ -32,6 +32,7 @@ sat = ["regions"]
32
32
  # all_cpu = ["regions", "bdsf", "TGEpy @ git+https://github.com/chrisfinlay/TGEpy.git"]
33
33
  ipy = ["ipython", "ipykernel"]
34
34
  test = ["pytest"]
35
+ docs = ["sphinx", "sphinx-rtd-theme"]
35
36
 
36
37
  [project.urls]
37
38
  Repository = "https://github.com/chrisfinlay/tab-sim.git"
@@ -5,7 +5,8 @@ import sys
5
5
  import shutil
6
6
 
7
7
  import collections.abc
8
- from typing import Tuple
8
+ from typing import Tuple, Union, Optional
9
+
9
10
  from datetime import datetime
10
11
 
11
12
  import dask.array as da
@@ -13,13 +14,14 @@ from dask.array.core import Array
13
14
  import xarray as xr
14
15
  import numpy as np
15
16
  import pandas as pd
17
+ from astropy.time import Time
16
18
 
17
19
  from tabsim.dask.observation import Observation
18
20
  from tabsim.sky import generate_random_sky
19
21
  from tabsim.plot import plot_uv, plot_src_alt, plot_angular_seps
20
22
  from tabsim.write import write_ms, mk_obs_name, mk_obs_dir
21
- from tabsim.jax.coordinates import calculate_fringe_frequency
22
- from tabsim.tle import get_visible_satellite_tles
23
+ from tabsim.jax.coordinates import calculate_fringe_frequency, jd_to_mjd
24
+ from tabsim.tle import get_visible_satellite_tles, id_generator
23
25
 
24
26
  from daskms import xds_from_ms
25
27
 
@@ -188,7 +190,7 @@ def load_sky_model(file_path: str, freqs: Array, src_type: str) -> tuple:
188
190
  raise KeyError("'src_type' must be one of {'point', 'gauss', 'exp'}.")
189
191
 
190
192
 
191
- def sigma_value(value: str | float | int, obs: Observation):
193
+ def sigma_value(value: Union[str, float, int], obs: Observation):
192
194
  try:
193
195
  if isinstance(value, str):
194
196
  sigma = (np.mean(obs.noise_std) / np.sqrt(obs.n_time * obs.n_bl)).compute()
@@ -212,12 +214,9 @@ def load_obs(obs_spec: dict) -> Observation:
212
214
  return x
213
215
 
214
216
  if obs_["start_time_jd"]:
215
- from tabascal.jax.coordinates import jd_to_mjd
216
217
 
217
218
  start_time_mjd = jd_to_mjd(obs_["start_time_jd"])
218
219
  elif obs_["start_time_isot"]:
219
- from astropy.time import Time
220
- from tabascal.jax.coordinates import jd_to_mjd
221
220
 
222
221
  start_time_mjd = jd_to_mjd(
223
222
  Time(obs_["start_time_isot"], format="isot", scale="ut1").jd
@@ -837,11 +836,14 @@ def print_fringe_freq_tle_sat(obs: Observation):
837
836
 
838
837
 
839
838
  def run_sim_config(
840
- obs_spec: dict = None, config_path: str = None, spacetrack_path: str = None
839
+ obs_spec: Optional[dict] = None,
840
+ config_path: Optional[str] = None,
841
+ spacetrack_path: Optional[str] = None,
841
842
  ) -> Tuple[Observation, str]:
842
843
 
843
844
  from tabsim.tle import id_generator
844
845
 
846
+
845
847
  log_path = f"log_sim_{id_generator()}.txt"
846
848
  log = open(log_path, "w")
847
849
  backup = sys.stdout
@@ -859,7 +861,7 @@ def run_sim_config(
859
861
  obs = load_obs(obs_spec)
860
862
  add_astro_sources(obs, obs_spec)
861
863
  add_satellite_sources(obs, obs_spec)
862
- if obs_spec["rfi_sources"]["tle_satellite"]["max_n_sat"] != 0:
864
+ if obs_spec["rfi_sources"]["tle_satellite"]["max_n_sat"] != 0 and spacetrack_path:
863
865
  add_tle_satellite_sources(obs, obs_spec, spacetrack_path)
864
866
  add_stationary_sources(obs, obs_spec)
865
867
  add_gains(obs, obs_spec)
@@ -1,6 +1,7 @@
1
1
  import jax.numpy as jnp
2
2
  from jax import jit, vmap, jacrev, config, Array
3
3
  from tabsim.jax.utils import jit_with_doc
4
+ from jax.lax import scan
4
5
 
5
6
  config.update("jax_enable_x64", True)
6
7
 
@@ -690,7 +691,7 @@ def calculate_fringe_frequency(
690
691
  r_ecef = xyz_to_itrf(rfi_xyz, gsa)
691
692
  s_ecef = r_ecef - jnp.mean(ants_itrf, axis=0)
692
693
  s_hat_ecef = s_ecef / jnp.linalg.norm(s_ecef, axis=-1, keepdims=True)
693
- s_hat_dot = jnp.gradient(s_hat_ecef, times, axis=0)
694
+ s_hat_dot = jnp.gradient(s_hat_ecef, jnp.diff(times[:2])[0], axis=0)
694
695
 
695
696
  a1, a2 = jnp.triu_indices(len(ants_itrf), 1)
696
697
  bl_ecef = ants_itrf[a1] - ants_itrf[a2]
@@ -1080,3 +1081,256 @@ def orbit_fisher(times: Array, orbit_params: Array, RIC_std: Array) -> Array:
1080
1081
  fisher = vmap(propagate, in_axes=(0, None))(J, F).sum(axis=0)
1081
1082
 
1082
1083
  return fisher
1084
+
1085
+
1086
+ # New Orbit Model with Keplerian Elements
1087
+
1088
+
1089
+ def R_z(angle):
1090
+ c, s = jnp.cos(angle), jnp.sin(angle)
1091
+ return jnp.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
1092
+
1093
+
1094
+ def R_x(angle):
1095
+ c, s = jnp.cos(angle), jnp.sin(angle)
1096
+ return jnp.array([[1, 0, 0], [0, c, -s], [0, s, c]])
1097
+
1098
+
1099
+ def solve_kepler(M, e, max_iter=100):
1100
+ """Newton-Raphson solver for Kepler's equation."""
1101
+
1102
+ def newton_step(E, _):
1103
+ f = E - e * jnp.sin(E) - M
1104
+ f_prime = 1 - e * jnp.cos(E)
1105
+ E_new = E - f / f_prime
1106
+ return E_new, E_new
1107
+
1108
+ E0 = M
1109
+ E_final, _ = scan(newton_step, E0, None, length=max_iter)
1110
+
1111
+ return E_final
1112
+
1113
+
1114
+ def kepler_orbit(times, t_epoch, params):
1115
+ """
1116
+ Compute orbit position over time for a single satellite.
1117
+
1118
+ times: (T,)
1119
+ params: (6,) = [a, e, i, Ω, ω, M0]
1120
+ Returns: (T, 3)
1121
+ """
1122
+ a, e, i_deg, Ω_deg, ω_deg, M0_deg = params
1123
+
1124
+ mu = 398600.4418 # km^3/s^2
1125
+
1126
+ n = jnp.sqrt(mu / jnp.abs(a) ** 3) * 86400
1127
+
1128
+ i = jnp.radians(i_deg)
1129
+ Ω = jnp.radians(Ω_deg)
1130
+ ω = jnp.radians(ω_deg)
1131
+ M_0 = jnp.radians(M0_deg)
1132
+
1133
+ M = M_0 + n * (times - t_epoch) # Mean anomaly
1134
+
1135
+ E = vmap(solve_kepler, (0, None))(jnp.atleast_1d(M), e)
1136
+
1137
+ nu = 2 * jnp.arctan2(
1138
+ jnp.sqrt(1 + e) * jnp.sin(E / 2), jnp.sqrt(1 - e) * jnp.cos(E / 2)
1139
+ )
1140
+
1141
+ r = a * (1 - e * jnp.cos(E))
1142
+ x_orb = r * jnp.cos(nu)
1143
+ y_orb = r * jnp.sin(nu)
1144
+ z_orb = jnp.zeros_like(x_orb)
1145
+
1146
+ R = R_z(Ω) @ R_x(i) @ R_z(ω)
1147
+
1148
+ pos_orb = jnp.stack([x_orb, y_orb, z_orb], axis=1) # (T, 3)
1149
+ pos_inertial = pos_orb @ R.T # (T, 3)
1150
+
1151
+ return pos_inertial * 1e3
1152
+
1153
+
1154
+ def kepler_orbit_many(
1155
+ times_jd: Array,
1156
+ epoch_jd: Array,
1157
+ elements: Array,
1158
+ # semi_major: Array,
1159
+ # eccentricity: Array,
1160
+ # inclination: Array,
1161
+ # ra_of_asc_node: Array,
1162
+ # arg_of_peri: Array,
1163
+ # mean_anomaly: Array,
1164
+ ):
1165
+ """Propagate Keplerian orbits for a set of satellites using orbital elements as defined in a TLE.
1166
+
1167
+ Parameters
1168
+ ----------
1169
+ times_jd : Array (n_time,)
1170
+ Propagation times in Julian Date.
1171
+ epoch_jd : Array (n_sat,)
1172
+ Epoch of the TLE in Julian Date.
1173
+ semi_major : Array (n_sat,)
1174
+ Semi major axis of the orbit in km.
1175
+ eccentricity : Array (n_sat,)
1176
+ Eccentricity of the orbit.
1177
+ inclination : Array (n_sat,)
1178
+ Inclination of the orbit in degrees.
1179
+ ra_of_asc_node : Array (n_sat,)
1180
+ Right ascension of the ascending node in degrees.
1181
+ arg_of_peri : Array (n_sat,)
1182
+ Argument of the pericentre in degrees.
1183
+ mean_anomaly : Array (n_sat,)
1184
+ Mean anomaly of the orbit at the epoch time in degrees.
1185
+ """
1186
+
1187
+ # elements = [
1188
+ # jnp.atleast_1d(x)
1189
+ # for x in [
1190
+ # semi_major,
1191
+ # eccentricity,
1192
+ # inclination,
1193
+ # ra_of_asc_node,
1194
+ # arg_of_peri,
1195
+ # mean_anomaly,
1196
+ # ]
1197
+ # ]
1198
+
1199
+ pos = vmap(kepler_orbit, (None, 0, 0))(times_jd, epoch_jd, elements)
1200
+
1201
+ return pos
1202
+
1203
+
1204
+ def kepler_orbit_velocity(times_jd: Array, epoch_jd: float, elements: Array) -> Array:
1205
+
1206
+ vel_vmap = vmap(jacrev(kepler_orbit, argnums=(0)), in_axes=(0, None, None))
1207
+ velocity = vel_vmap(times_jd, epoch_jd, elements).reshape(len(times_jd), 3)
1208
+
1209
+ return velocity
1210
+
1211
+
1212
+ def R_uvw(
1213
+ times_jd: Array,
1214
+ epoch_jd: float,
1215
+ elements: Array,
1216
+ ) -> Array:
1217
+ """
1218
+ Calculate the rotation matrices at each time step to transform from an Earth
1219
+ centric reference frame (ECI) to a satellite centric frame given the
1220
+ parameters of its (circular) orbit.
1221
+
1222
+ Parameters
1223
+ ----------
1224
+ times: ndarray (n_time,)
1225
+ Times at which to evaluate the rotation matrices.
1226
+ elevation: float
1227
+ Elevation/Altitude of the orbit in metres.
1228
+ inclination: float
1229
+ Inclination angle of the orbit relative to the equatorial plane.
1230
+ lon_asc_node: float
1231
+ Longitude of the ascending node of the orbit. This is the longitude of
1232
+ when the orbit crosses the equator from the south to the north.
1233
+ periapsis: float
1234
+ Perisapsis of the orbit. This is the angular starting point of the orbit
1235
+ at t = 0.
1236
+
1237
+ Returns
1238
+ -------
1239
+ R: ndarray (3, 3)
1240
+ The rotation matrix to orient to a satellite centric (RIC) frame defined
1241
+ by the Radial, In-track, Cross-track components.
1242
+ """
1243
+ times_jd = jnp.asarray(times_jd)
1244
+ elements = jnp.asarray(elements)
1245
+
1246
+ position = kepler_orbit(times_jd, epoch_jd, elements)
1247
+ velocity = kepler_orbit_velocity(times_jd, epoch_jd, elements)
1248
+
1249
+ vel_hat = velocity / jnp.linalg.norm(velocity, axis=-1, keepdims=True)
1250
+ u_hat = position / jnp.linalg.norm(position, axis=-1, keepdims=True)
1251
+ w_hat = jnp.cross(u_hat, vel_hat)
1252
+ v_hat = jnp.cross(w_hat, u_hat)
1253
+ R = jnp.stack([u_hat.T, v_hat.T, w_hat.T])
1254
+
1255
+ return R
1256
+
1257
+
1258
+ def kepler_RIC_dev(
1259
+ times_jd: Array,
1260
+ epoch_jd: float,
1261
+ true_elements: Array,
1262
+ estimated_elements: Array,
1263
+ ) -> Array:
1264
+ """
1265
+ Calculate the Radial (R), In-track (I) and Cross-track (C) deviations
1266
+ between two circular orbits given their orbit parameters at many time steps.
1267
+
1268
+ Parameters
1269
+ ----------
1270
+ times: array (n_time,)
1271
+ Times at which to evaluate the RIC deviations.
1272
+ true_orb_params: ndarray (4,)
1273
+ Orbit parameters (elevation, inclination, lon_asc_node, periapsis) of
1274
+ the object of most interest. The more accurate measurement when
1275
+ comparing the same object.
1276
+ est_orb_params: array (elevation, inclination, lon_asc_node, periapsis)
1277
+ Estimated orbit parameters.
1278
+
1279
+ Returns
1280
+ -------
1281
+ RIC: ndarray (n_time, 3)
1282
+ Radial, In-track and Cross-track coordinates, in metres, of the orbiting
1283
+ body relative to the orbit defined by 'true_orbit_params'.
1284
+ """
1285
+ times_jd = jnp.asarray(times_jd)
1286
+ true_elements = jnp.asarray(true_elements)
1287
+ estimated_elements = jnp.asarray(estimated_elements)
1288
+
1289
+ true_xyz = kepler_orbit(times_jd, epoch_jd, true_elements)
1290
+ est_xyz = kepler_orbit(times_jd, epoch_jd, estimated_elements)
1291
+
1292
+ R = R_uvw(times_jd, epoch_jd, true_elements)
1293
+ true_uvw = vmap(lambda x, y: x @ y, in_axes=(-1, 0))(R, true_xyz)
1294
+ est_uvw = vmap(lambda x, y: x @ y, in_axes=(-1, 0))(R, est_xyz)
1295
+
1296
+ return est_uvw - true_uvw
1297
+
1298
+
1299
+ def kepler_orbit_fisher(
1300
+ times_jd: Array, epoch_jd: float, elements: Array, RIC_std: Array
1301
+ ) -> Array:
1302
+ """
1303
+ Calculate the inverse covariance (Fisher) matrix in orbital elements
1304
+ induced by errors in the RIC frame of an orbiting object. This is
1305
+ essentially linear uncertainty propagation assuming Gaussian errors.
1306
+
1307
+ Parameters
1308
+ ----------
1309
+ times: ndarray (n_time,)
1310
+ Times at which the RIC covariance/standard deviations is defined.
1311
+ orbit_params: ndarray (4,)
1312
+ Orbit parameters (elevation, inclination, lon_asc_node, periapsis) for
1313
+ the orbit defining the origin of the RIC frame.
1314
+ RIC_std: ndarray (3,)
1315
+ The standard deviation in the Radial, In-track and Cross-track
1316
+ directions.
1317
+
1318
+ Returns
1319
+ -------
1320
+ fisher: ndarray (6, 6)
1321
+ The inverse covariance (Fisher) matrix for the orbit parameters induced
1322
+ by the orbit uncertainties in the RIC frame.
1323
+ """
1324
+ times_jd = jnp.asarray(times_jd)
1325
+ elements = jnp.asarray(elements)
1326
+ RIC_std = jnp.asarray(RIC_std)
1327
+
1328
+ J = jacrev(kepler_RIC_dev, argnums=(3,))(times_jd, epoch_jd, elements, elements)[0]
1329
+ F = jnp.diag(1.0 / RIC_std**2)
1330
+
1331
+ def propagate(J, F):
1332
+ return J.T @ F @ J
1333
+
1334
+ fisher = vmap(propagate, in_axes=(0, None))(J, F).sum(axis=0)
1335
+
1336
+ return fisher
@@ -5,8 +5,10 @@ from pathlib import Path
5
5
 
6
6
  from tabsim.config import load_config, run_sim_config
7
7
 
8
+ from typing import Union
8
9
 
9
- def get_abs_path(rel_path: str | None, work_dir: str) -> str | None:
10
+
11
+ def get_abs_path(rel_path: Union[str, None], work_dir: str) -> Union[str, None]:
10
12
  if rel_path:
11
13
  return os.path.abspath(os.path.join(work_dir, rel_path))
12
14
  else:
@@ -2,8 +2,10 @@ import numpy as np
2
2
  import jax.numpy as jnp
3
3
  import dask.array as da
4
4
 
5
+ from typing import Optional
5
6
 
6
- def uniform_points_disk(radius: float, n_src: int, random_seed: int | None = None):
7
+
8
+ def uniform_points_disk(radius: float, n_src: int, random_seed: Optional[int] = None):
7
9
  """
8
10
  Generate uniformly distributed random points on a disk.
9
11
 
@@ -21,8 +21,10 @@ from glob import glob
21
21
 
22
22
  from importlib.resources import files
23
23
 
24
+ from typing import Optional
24
25
 
25
- def make_tle_dir(tle_dir: str | None):
26
+
27
+ def make_tle_dir(tle_dir: Optional[str]):
26
28
 
27
29
  if tle_dir:
28
30
  tle_dir = os.path.abspath(tle_dir)
@@ -89,7 +91,7 @@ def get_tles_by_id(
89
91
  epoch_jd: float,
90
92
  window_days: float = 1.0,
91
93
  limit: int = 2000,
92
- tle_dir: str | None = None,
94
+ tle_dir: Optional[str] = None,
93
95
  ) -> pd.DataFrame:
94
96
 
95
97
  tle_dir = make_tle_dir(tle_dir)
@@ -166,7 +168,7 @@ def get_tles_by_name(
166
168
  epoch_jd: float,
167
169
  window_days: float = 1.0,
168
170
  limit: int = 10000,
169
- tle_dir: str | None = None,
171
+ tle_dir: Optional[str] = None,
170
172
  ) -> pd.DataFrame:
171
173
 
172
174
  tle_dir = make_tle_dir(tle_dir)
@@ -291,7 +293,7 @@ def get_visible_satellite_tles(
291
293
  min_elevation: float,
292
294
  names: ArrayLike = [],
293
295
  norad_ids: ArrayLike = [],
294
- tle_dir: str | None = None,
296
+ tle_dir: Optional[str] = None,
295
297
  ) -> tuple:
296
298
  """Get the TLEs corresponding to satellites that satisfy the conditions given.
297
299
 
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
3
+ from typing import TYPE_CHECKING, Optional
4
4
 
5
5
  if TYPE_CHECKING:
6
6
  from tabsim.dask.observation import Observation
@@ -12,9 +12,11 @@ import dask
12
12
  import dask.array as da
13
13
  import numpy as np
14
14
  import xarray as xr
15
- from daskms import Dataset, xds_to_table
15
+ from daskms import Dataset, xds_to_table, xds_from_ms
16
16
  import numpy as np
17
17
 
18
+ from dask.array.core import Array
19
+
18
20
 
19
21
  def rm_dir(path: str, overwrite: bool = True):
20
22
  """Remove a directory but check for existence and overwrite flag before.
@@ -73,7 +75,7 @@ def mk_obs_dir(output_path: str, obs_name: str, overwrite: bool = True) -> tuple
73
75
  return save_path, zarr_path, ms_path
74
76
 
75
77
 
76
- def mk_obs_name(prefix: str, obs: Observation, suffix: str = None) -> str:
78
+ def mk_obs_name(prefix: str, obs: Observation, suffix: Optional[str] = None) -> str:
77
79
  """Construct an observation name based on the parameters of the observation and an additional prefix.
78
80
 
79
81
  Parameters
@@ -381,6 +383,40 @@ def get_stationary_rfi_data(obs: Observation):
381
383
  return rfi_stat
382
384
 
383
385
 
386
+ def add_to_ms(ds: Dataset, ms_path: str, flags: Optional[Array] = None):
387
+
388
+ n_time = ds.attrs["n_time"]
389
+ n_freq = ds.attrs["n_freq"]
390
+ n_corr = 1
391
+ n_bl = ds.attrs["n_bl"]
392
+ n_row = n_time * n_bl
393
+
394
+ xds_ms = xds_from_ms(ms_path)[0]
395
+
396
+ dims = ["row", "chan", "corr"]
397
+ chunks = {k: v for k, v in xds_ms.chunks.items() if k in dims}
398
+
399
+ vis_rfi = ds.vis_rfi.data.reshape(n_row, n_freq, n_corr)
400
+ vis_rfi = xr.DataArray(
401
+ da.stack(
402
+ [vis_rfi, da.zeros_like(vis_rfi), da.zeros_like(vis_rfi), vis_rfi], axis=2
403
+ ),
404
+ dims=("row", "chan", "corr"),
405
+ )
406
+
407
+ xds_ms = xds_ms.assign(DATA=xds_ms.DATA + vis_rfi)
408
+ cols = "DATA"
409
+
410
+ if flags:
411
+ flags = xr.DataArray(
412
+ ds.flags.data.reshape(n_row, n_freq, n_corr), dims=("row", "chan", "corr")
413
+ )
414
+ xds_ms = xds_ms.assign(FLAG=flags)
415
+ cols.append("FLAG")
416
+
417
+ dask.compute(xds_to_table([xds_ms], ms_path, cols))
418
+
419
+
384
420
  def write_ms(
385
421
  ds: Dataset,
386
422
  ms_path: str,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tabsim
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: Trajectory-based Radio Frequency Interference (RFI) simulations for radio interferometeric data.
5
5
  Author-email: Chris Finlay <christopher.finlay@epfl.ch>
6
6
  Maintainer-email: Chris Finlay <christopher.finlay@epfl.ch>
@@ -680,7 +680,7 @@ License: GNU GENERAL PUBLIC LICENSE
680
680
  <https://www.gnu.org/licenses/why-not-lgpl.html>.
681
681
 
682
682
  Project-URL: Repository, https://github.com/chrisfinlay/tab-sim.git
683
- Requires-Python: <3.13,>=3.10
683
+ Requires-Python: <3.13,>=3.9
684
684
  Description-Content-Type: text/markdown
685
685
  License-File: LICENSE
686
686
  Requires-Dist: jax
@@ -704,12 +704,16 @@ Requires-Dist: ipython; extra == "ipy"
704
704
  Requires-Dist: ipykernel; extra == "ipy"
705
705
  Provides-Extra: test
706
706
  Requires-Dist: pytest; extra == "test"
707
+ Provides-Extra: docs
708
+ Requires-Dist: sphinx; extra == "docs"
709
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
707
710
  Dynamic: license-file
708
711
 
709
712
  # tab-sim
710
713
 
711
714
  [![DOI:10.1093/mnras/stad1979](https://zenodo.org/badge/DOI/10.1093/mnras/stad1979.svg)](https://doi.org/10.1093/mnras/stad1979)
712
715
  [![DOI:10.48550/arXiv.2502.00106](https://img.shields.io/badge/arXiv-2502.00106-b31b1b.svg)](https://doi.org/10.48550/arXiv.2502.00106)
716
+ [![Documentation Status](https://readthedocs.org/projects/tab-sim/badge/?version=latest)](https://tab-sim.readthedocs.io/en/latest/?badge=latest)
713
717
 
714
718
  **T**r**A**jectory **B**ased RFI **Sim**ulations of radio interferometry data.
715
719
  A source to visibility model for RFI sources including certain near-field effects.
@@ -717,6 +721,10 @@ A source to visibility model for RFI sources including certain near-field effect
717
721
  `tab-sim` is written in [JAX](https://jax.readthedocs.io/en/latest/notebooks/quickstart.html)
718
722
  and [Dask](https://www.dask.org) and can therefore use GPUs and/or CPUs to perform larger than memory computation.
719
723
 
724
+ ## Documentation
725
+
726
+ [https://tab-sim.readthedocs.io/](https://tab-sim.readthedocs.io/)
727
+
720
728
  ## Installation
721
729
 
722
730
  The following instructions are expected to work on Linux machine. If you are running Windows it is recommended to use WSL. If you are running Mac then it is best to start with a [conda](https://docs.conda.io/en/latest/)/[mamba](https://mamba.readthedocs.io/en/latest/index.html) environment and install `python-casacore` before continuing with the `pip` install method. If all else fails there is the [Docker install](#docker).
@@ -1,6 +1,7 @@
1
1
  LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
+ docs/conf.py
4
5
  tabsim/__init__.py
5
6
  tabsim/config.py
6
7
  tabsim/plot.py
@@ -11,6 +11,10 @@ spacetrack
11
11
  skyfield
12
12
  numpy<2.0.0
13
13
 
14
+ [docs]
15
+ sphinx
16
+ sphinx-rtd-theme
17
+
14
18
  [gpu]
15
19
  jax[cuda12]
16
20
 
@@ -1,4 +1,5 @@
1
1
  dist
2
+ docs
2
3
  examples
3
4
  tabsim
4
5
  tests
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes