tabsim 0.0.1__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 (50) hide show
  1. {tabsim-0.0.1 → tabsim-0.0.3}/PKG-INFO +11 -1
  2. {tabsim-0.0.1 → tabsim-0.0.3}/README.md +5 -0
  3. tabsim-0.0.3/docs/conf.py +14 -0
  4. {tabsim-0.0.1 → tabsim-0.0.3}/pyproject.toml +3 -1
  5. tabsim-0.0.3/tabsim/__init__.py +6 -0
  6. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/config.py +11 -9
  7. tabsim-0.0.3/tabsim/data/aux_data/tles/2023-02-21-J8HE0T.json +1 -0
  8. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/jax/coordinates.py +255 -1
  9. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/scripts/sim_vis.py +10 -1
  10. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/sky.py +3 -1
  11. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/tle.py +6 -4
  12. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/write.py +39 -3
  13. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim.egg-info/PKG-INFO +11 -1
  14. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim.egg-info/SOURCES.txt +6 -1
  15. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim.egg-info/requires.txt +7 -0
  16. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim.egg-info/top_level.txt +2 -0
  17. tabsim-0.0.3/tests/test_basic.py +5 -0
  18. tabsim-0.0.3/tests/test_sim-vis.py +40 -0
  19. tabsim-0.0.3/tests/test_version.py +6 -0
  20. tabsim-0.0.1/tabsim/jax/__init__.py +0 -0
  21. {tabsim-0.0.1 → tabsim-0.0.3}/LICENSE +0 -0
  22. {tabsim-0.0.1 → tabsim-0.0.3}/setup.cfg +0 -0
  23. {tabsim-0.0.1/tabsim → tabsim-0.0.3/tabsim/dask}/__init__.py +0 -0
  24. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/dask/coordinates.py +0 -0
  25. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/dask/extras.py +0 -0
  26. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/dask/interferometry.py +0 -0
  27. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/dask/observation.py +0 -0
  28. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/data/EDA2.itrf.txt +0 -0
  29. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/data/MWA.itrf.txt +0 -0
  30. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/data/MeerKAT.itrf.txt +0 -0
  31. {tabsim-0.0.1/tabsim/dask → tabsim-0.0.3/tabsim/data}/__init__.py +0 -0
  32. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/data/aux_data/calibrator.skymodel +0 -0
  33. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/data/aux_data/norad_satellite.rfimodel +0 -0
  34. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/data/aux_data/satellite.ole +0 -0
  35. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/data/aux_data/satellite.rfimodel +0 -0
  36. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/data/aux_data/stationary.loc +0 -0
  37. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/data/aux_data/stationary.rfimodel +0 -0
  38. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/data/config_files/sim_config_base.yaml +0 -0
  39. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/data/config_files/tab_config_base.yaml +0 -0
  40. {tabsim-0.0.1/tabsim/data → tabsim-0.0.3/tabsim/jax}/__init__.py +0 -0
  41. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/jax/interferometry.py +0 -0
  42. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/jax/utils.py +0 -0
  43. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/plot.py +0 -0
  44. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/scripts/__init__.py +0 -0
  45. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/scripts/ast_region.py +0 -0
  46. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/scripts/flag_data.py +0 -0
  47. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/scripts/tle_sat_region.py +0 -0
  48. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim/tools.py +0 -0
  49. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim.egg-info/dependency_links.txt +0 -0
  50. {tabsim-0.0.1 → tabsim-0.0.3}/tabsim.egg-info/entry_points.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tabsim
3
- Version: 0.0.1
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>
@@ -702,12 +702,18 @@ Requires-Dist: regions; extra == "sat"
702
702
  Provides-Extra: ipy
703
703
  Requires-Dist: ipython; extra == "ipy"
704
704
  Requires-Dist: ipykernel; extra == "ipy"
705
+ Provides-Extra: test
706
+ Requires-Dist: pytest; extra == "test"
707
+ Provides-Extra: docs
708
+ Requires-Dist: sphinx; extra == "docs"
709
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
705
710
  Dynamic: license-file
706
711
 
707
712
  # tab-sim
708
713
 
709
714
  [![DOI:10.1093/mnras/stad1979](https://zenodo.org/badge/DOI/10.1093/mnras/stad1979.svg)](https://doi.org/10.1093/mnras/stad1979)
710
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)
711
717
 
712
718
  **T**r**A**jectory **B**ased RFI **Sim**ulations of radio interferometry data.
713
719
  A source to visibility model for RFI sources including certain near-field effects.
@@ -715,6 +721,10 @@ A source to visibility model for RFI sources including certain near-field effect
715
721
  `tab-sim` is written in [JAX](https://jax.readthedocs.io/en/latest/notebooks/quickstart.html)
716
722
  and [Dask](https://www.dask.org) and can therefore use GPUs and/or CPUs to perform larger than memory computation.
717
723
 
724
+ ## Documentation
725
+
726
+ [https://tab-sim.readthedocs.io/](https://tab-sim.readthedocs.io/)
727
+
718
728
  ## Installation
719
729
 
720
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.1"
8
+ version = "0.0.3"
9
9
  license = {file = "LICENSE"}
10
10
  readme = "README.md"
11
11
  # dynamic = ["version", "readme"]
@@ -31,6 +31,8 @@ sat = ["regions"]
31
31
  # all = ["jax[cuda12]", "regions", "bdsf", "TGEpy @ git+https://github.com/chrisfinlay/TGEpy.git"]
32
32
  # all_cpu = ["regions", "bdsf", "TGEpy @ git+https://github.com/chrisfinlay/TGEpy.git"]
33
33
  ipy = ["ipython", "ipykernel"]
34
+ test = ["pytest"]
35
+ docs = ["sphinx", "sphinx-rtd-theme"]
34
36
 
35
37
  [project.urls]
36
38
  Repository = "https://github.com/chrisfinlay/tab-sim.git"
@@ -0,0 +1,6 @@
1
+ try:
2
+ from importlib.metadata import version
3
+ except ImportError:
4
+ from importlib_metadata import version
5
+
6
+ __version__ = version("tabsim")
@@ -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)
@@ -0,0 +1 @@
1
+ {"COMMENT":{"0":"GENERATED VIA SPACETRACK.ORG API","1":"GENERATED VIA SPACETRACK.ORG API","2":"GENERATED VIA SPACETRACK.ORG API","3":"GENERATED VIA SPACETRACK.ORG API","4":"GENERATED VIA SPACETRACK.ORG API","5":"GENERATED VIA SPACETRACK.ORG API","6":"GENERATED VIA SPACETRACK.ORG API","7":"GENERATED VIA SPACETRACK.ORG API"},"ORIGINATOR":{"0":"18 SPCS","1":"18 SPCS","2":"18 SPCS","3":"18 SPCS","4":"18 SPCS","5":"18 SPCS","6":"18 SPCS","7":"18 SPCS"},"NORAD_CAT_ID":{"0":"20452","1":"38833","2":"38833","3":"38833","4":"38833","5":"45854","6":"45854","7":"45854"},"OBJECT_NAME":{"0":"NAVSTAR 18 (USA 50)","1":"NAVSTAR 67 (USA 239)","2":"NAVSTAR 67 (USA 239)","3":"NAVSTAR 67 (USA 239)","4":"NAVSTAR 67 (USA 239)","5":"NAVSTAR 79 (USA 304)","6":"NAVSTAR 79 (USA 304)","7":"NAVSTAR 79 (USA 304)"},"OBJECT_TYPE":{"0":"PAYLOAD","1":"PAYLOAD","2":"PAYLOAD","3":"PAYLOAD","4":"PAYLOAD","5":"PAYLOAD","6":"PAYLOAD","7":"PAYLOAD"},"CLASSIFICATION_TYPE":{"0":"U","1":"U","2":"U","3":"U","4":"U","5":"U","6":"U","7":"U"},"INTLDES":{"0":"90008A","1":"12053A","2":"12053A","3":"12053A","4":"12053A","5":"20041A","6":"20041A","7":"20041A"},"EPOCH":{"0":"2023-02-21 06:38:50","1":"2023-02-21 06:37:41","2":"2023-02-21 06:37:41","3":"2023-02-21 06:37:41","4":"2023-02-22 06:33:35","5":"2023-02-21 08:40:26","6":"2023-02-22 10:18:13","7":"2023-02-22 10:18:13"},"EPOCH_MICROSECONDS":{"0":"12736","1":"186496","2":"186496","3":"189952","4":"104608","5":"76288","6":"882176","7":"882176"},"MEAN_MOTION":{"0":"1.89597679","1":"2.0056438","2":"2.0056438","3":"2.00564379","4":"2.00564407","5":"2.00566443","6":"2.00566487","7":"2.00566487"},"ECCENTRICITY":{"0":"0.0013992","1":"0.0135647","2":"0.0135647","3":"0.013565","4":"0.0135663","5":"0.0030268","6":"0.0030262","7":"0.0030262"},"INCLINATION":{"0":"54.7164","1":"53.518","2":"53.518","3":"53.518","4":"53.5179","5":"55.7203","6":"55.7213","7":"55.7213"},"RA_OF_ASC_NODE":{"0":"175.9594","1":"193.1658","2":"193.1658","3":"193.1658","4":"193.125","5":"76.4335","6":"76.391","7":"76.391"},"ARG_OF_PERICENTER":{"0":"264.8777","1":"50.1444","2":"50.1444","3":"50.1442","4":"50.1485","5":"186.8094","6":"186.8192","7":"186.8192"},"MEAN_ANOMALY":{"0":"93.0539","1":"310.9883","2":"310.9883","3":"310.9885","4":"310.9878","5":"299.724","6":"350.8122","7":"350.8122"},"EPHEMERIS_TYPE":{"0":"0","1":"0","2":"0","3":"0","4":"0","5":"0","6":"0","7":"0"},"ELEMENT_SET_NO":{"0":"999","1":"999","2":"999","3":"999","4":"999","5":"999","6":"999","7":"999"},"REV_AT_EPOCH":{"0":"23344","1":"7602","2":"7513","3":"7513","4":"7515","5":"1973","6":"1974","7":"1975"},"BSTAR":{"0":"0","1":"0","2":"0","3":"0","4":"0","5":"0","6":"0","7":"0"},"MEAN_MOTION_DOT":{"0":"0.00000009","1":"0.00000013","2":"0.00000013","3":"0.00000013","4":"0.0000002","5":"-0.00000013","6":"-0.00000024","7":"-0.00000024"},"MEAN_MOTION_DDOT":{"0":"0","1":"0","2":"0","3":"0","4":"0","5":"0","6":"0","7":"0"},"FILE":{"0":"3819803","1":"3818603","2":"3819803","3":"3821329","4":"3824148","5":"3821329","6":"3823581","7":"3824148"},"TLE_LINE0":{"0":"0 NAVSTAR 18 (USA 50)","1":"0 NAVSTAR 67 (USA 239)","2":"0 NAVSTAR 67 (USA 239)","3":"0 NAVSTAR 67 (USA 239)","4":"0 NAVSTAR 67 (USA 239)","5":"0 NAVSTAR 79 (USA 304)","6":"0 NAVSTAR 79 (USA 304)","7":"0 NAVSTAR 79 (USA 304)"},"TLE_LINE1":{"0":"1 20452U 90008A 23052.27696774 .00000009 00000-0 00000-0 0 9999","1":"1 38833U 12053A 23052.27617114 .00000013 00000-0 00000+0 0 9990","2":"1 38833U 12053A 23052.27617114 .00000013 00000-0 00000-0 0 9991","3":"1 38833U 12053A 23052.27617118 .00000013 00000-0 00000-0 0 9995","4":"1 38833U 12053A 23053.27332297 .00000020 00000-0 00000-0 0 9996","5":"1 45854U 20041A 23052.36141292 -.00000013 00000-0 00000-0 0 9998","6":"1 45854U 20041A 23053.42932734 -.00000024 00000-0 00000+0 0 9996","7":"1 45854U 20041A 23053.42932734 -.00000024 00000-0 00000-0 0 9997"},"TLE_LINE2":{"0":"2 20452 54.7164 175.9594 0013992 264.8777 93.0539 1.89597679233443","1":"2 38833 53.5180 193.1658 0135647 50.1444 310.9883 2.00564380 76021","2":"2 38833 53.5180 193.1658 0135647 50.1444 310.9883 2.00564380 75132","3":"2 38833 53.5180 193.1658 0135650 50.1442 310.9885 2.00564379 75134","4":"2 38833 53.5179 193.1250 0135663 50.1485 310.9878 2.00564407 75157","5":"2 45854 55.7203 76.4335 0030268 186.8094 299.7240 2.00566443 19736","6":"2 45854 55.7213 76.3910 0030262 186.8192 350.8122 2.00566487 19745","7":"2 45854 55.7213 76.3910 0030262 186.8192 350.8122 2.00566487 19756"},"OBJECT_ID":{"0":"1990-008A","1":"2012-053A","2":"2012-053A","3":"2012-053A","4":"2012-053A","5":"2020-041A","6":"2020-041A","7":"2020-041A"},"OBJECT_NUMBER":{"0":"20452","1":"38833","2":"38833","3":"38833","4":"38833","5":"45854","6":"45854","7":"45854"},"SEMIMAJOR_AXIS":{"0":"27574.850","1":"26560.279","2":"26560.279","3":"26560.279","4":"26560.277","5":"26560.097","6":"26560.093","7":"26560.093"},"PERIOD":{"0":"759.503","1":"717.974","2":"717.974","3":"717.974","4":"717.974","5":"717.967","6":"717.966","7":"717.966"},"APOGEE":{"0":"21235.298","1":"20542.427","2":"20542.427","3":"20542.435","4":"20542.467","5":"20262.354","6":"20262.334","7":"20262.334"},"PERIGEE":{"0":"21158.133","1":"19821.862","2":"19821.862","3":"19821.854","4":"19821.817","5":"20101.570","6":"20101.582","7":"20101.582"},"DECAYED":{"0":"0","1":"0","2":"0","3":"0","4":"0","5":"0","6":"0","7":"0"},"Fetch_Timestamp":{"0":"2025-06-12T20:52:43.524","1":"2025-06-12T20:52:43.524","2":"2025-06-12T20:52:43.524","3":"2025-06-12T20:52:43.524","4":"2025-06-12T20:52:43.524","5":"2025-06-12T20:52:43.524","6":"2025-06-12T20:52:43.524","7":"2025-06-12T20:52:43.524"}}
@@ -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
@@ -1,10 +1,14 @@
1
1
  import argparse
2
2
  import os
3
+ import sys
4
+ from pathlib import Path
3
5
 
4
6
  from tabsim.config import load_config, run_sim_config
5
7
 
8
+ from typing import Union
6
9
 
7
- 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]:
8
12
  if rel_path:
9
13
  return os.path.abspath(os.path.join(work_dir, rel_path))
10
14
  else:
@@ -63,6 +67,11 @@ def main():
63
67
  args = parser.parse_args()
64
68
  rfi_amp = args.rfi_amp
65
69
  spacetrack_path = args.spacetrack
70
+ config_path = Path(args.config_path)
71
+
72
+ if not config_path.is_file():
73
+ print(f"Error: config file not found: {config_path}", file=sys.stderr)
74
+ sys.exit(1) # Exit with error code
66
75
 
67
76
  config_path = os.path.abspath(args.config_path)
68
77
  work_dir = os.path.split(config_path)[0]
@@ -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.1
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>
@@ -702,12 +702,18 @@ Requires-Dist: regions; extra == "sat"
702
702
  Provides-Extra: ipy
703
703
  Requires-Dist: ipython; extra == "ipy"
704
704
  Requires-Dist: ipykernel; extra == "ipy"
705
+ Provides-Extra: test
706
+ Requires-Dist: pytest; extra == "test"
707
+ Provides-Extra: docs
708
+ Requires-Dist: sphinx; extra == "docs"
709
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
705
710
  Dynamic: license-file
706
711
 
707
712
  # tab-sim
708
713
 
709
714
  [![DOI:10.1093/mnras/stad1979](https://zenodo.org/badge/DOI/10.1093/mnras/stad1979.svg)](https://doi.org/10.1093/mnras/stad1979)
710
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)
711
717
 
712
718
  **T**r**A**jectory **B**ased RFI **Sim**ulations of radio interferometry data.
713
719
  A source to visibility model for RFI sources including certain near-field effects.
@@ -715,6 +721,10 @@ A source to visibility model for RFI sources including certain near-field effect
715
721
  `tab-sim` is written in [JAX](https://jax.readthedocs.io/en/latest/notebooks/quickstart.html)
716
722
  and [Dask](https://www.dask.org) and can therefore use GPUs and/or CPUs to perform larger than memory computation.
717
723
 
724
+ ## Documentation
725
+
726
+ [https://tab-sim.readthedocs.io/](https://tab-sim.readthedocs.io/)
727
+
718
728
  ## Installation
719
729
 
720
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
@@ -29,6 +30,7 @@ tabsim/data/aux_data/satellite.ole
29
30
  tabsim/data/aux_data/satellite.rfimodel
30
31
  tabsim/data/aux_data/stationary.loc
31
32
  tabsim/data/aux_data/stationary.rfimodel
33
+ tabsim/data/aux_data/tles/2023-02-21-J8HE0T.json
32
34
  tabsim/data/config_files/sim_config_base.yaml
33
35
  tabsim/data/config_files/tab_config_base.yaml
34
36
  tabsim/jax/__init__.py
@@ -39,4 +41,7 @@ tabsim/scripts/__init__.py
39
41
  tabsim/scripts/ast_region.py
40
42
  tabsim/scripts/flag_data.py
41
43
  tabsim/scripts/sim_vis.py
42
- tabsim/scripts/tle_sat_region.py
44
+ tabsim/scripts/tle_sat_region.py
45
+ tests/test_basic.py
46
+ tests/test_sim-vis.py
47
+ tests/test_version.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
 
@@ -20,3 +24,6 @@ ipykernel
20
24
 
21
25
  [sat]
22
26
  regions
27
+
28
+ [test]
29
+ pytest
@@ -1,3 +1,5 @@
1
1
  dist
2
+ docs
2
3
  examples
3
4
  tabsim
5
+ tests
@@ -0,0 +1,5 @@
1
+ def test_import():
2
+ import tabsim
3
+ assert hasattr(tabsim, "__version__")
4
+
5
+
@@ -0,0 +1,40 @@
1
+ import sys
2
+ import pytest
3
+ from unittest.mock import patch
4
+ from pathlib import Path
5
+ from tabsim.scripts import sim_vis
6
+
7
+
8
+ def test_sim_vis_help_exists(capsys):
9
+ test_args = ["sim-vis", "--help"]
10
+
11
+ with patch.object(sys, "argv", test_args):
12
+ with pytest.raises(SystemExit) as e:
13
+ sim_vis.main()
14
+
15
+ assert e.value.code == 0 # argparse exits with code 0 for --help
16
+ out = capsys.readouterr().out
17
+ assert "usage:" in out.lower()
18
+ assert "--help" in out
19
+
20
+
21
+ def test_missing_config_file():
22
+ args = ["sim-vis", "--config", "nonexistent.yaml"]
23
+
24
+ with patch.object(sys, "argv", args), pytest.raises(SystemExit):
25
+ sim_vis.main()
26
+
27
+
28
+ def test_simulation_runs_with_config(capsys):
29
+ config_path = (
30
+ Path(__file__).parent.parent / "examples" / "test" / "sim_test_16A.yaml"
31
+ )
32
+
33
+ assert config_path.is_file(), f"Missing config file: {config_path}"
34
+ args = ["sim-vis", "--config", str(config_path), "-o"]
35
+
36
+ with patch.object(sys, "argv", args):
37
+ sim_vis.main()
38
+
39
+ output = capsys.readouterr().out
40
+ assert "Total simulation time" in output
@@ -0,0 +1,6 @@
1
+ import tabsim
2
+
3
+ def test_version_format():
4
+ assert isinstance(tabsim.__version__, str)
5
+ assert len(tabsim.__version__) > 0
6
+ assert tabsim.__version__.count('.') >= 1 # basic semantic versioning check
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