tabsim 0.0.4__tar.gz → 0.0.5__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 (53) hide show
  1. {tabsim-0.0.4 → tabsim-0.0.5}/PKG-INFO +4 -4
  2. {tabsim-0.0.4 → tabsim-0.0.5}/pyproject.toml +5 -4
  3. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/config.py +37 -8
  4. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/dask/observation.py +19 -6
  5. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/data/config/sim_config_base.yaml +2 -0
  6. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/jax/coordinates.py +55 -0
  7. tabsim-0.0.5/tabsim/read_ms.py +137 -0
  8. tabsim-0.0.5/tabsim/scripts/write_config_from_ms.py +40 -0
  9. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/write.py +57 -23
  10. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim.egg-info/PKG-INFO +4 -4
  11. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim.egg-info/SOURCES.txt +2 -0
  12. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim.egg-info/entry_points.txt +1 -0
  13. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim.egg-info/requires.txt +2 -2
  14. {tabsim-0.0.4 → tabsim-0.0.5}/LICENSE +0 -0
  15. {tabsim-0.0.4 → tabsim-0.0.5}/README.md +0 -0
  16. {tabsim-0.0.4 → tabsim-0.0.5}/docs/conf.py +0 -0
  17. {tabsim-0.0.4 → tabsim-0.0.5}/setup.cfg +0 -0
  18. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/__init__.py +0 -0
  19. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/dask/__init__.py +0 -0
  20. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/dask/coordinates.py +0 -0
  21. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/dask/extras.py +0 -0
  22. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/dask/interferometry.py +0 -0
  23. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/data/__init__.py +0 -0
  24. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/data/config/tab_config_base.yaml +0 -0
  25. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/data/rfi/norad_satellite.rfimodel +0 -0
  26. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/data/rfi/satellite.ole +0 -0
  27. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/data/rfi/satellite.rfimodel +0 -0
  28. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/data/rfi/stationary.loc +0 -0
  29. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/data/rfi/stationary.rfimodel +0 -0
  30. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/data/rfi/tles/2023-02-21-J8HE0T.json +0 -0
  31. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/data/rfi/tles/2023-02-21-navstar.json +0 -0
  32. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/data/sky/calibrator.skymodel +0 -0
  33. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/data/telescopes/EDA2.itrf.txt +0 -0
  34. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/data/telescopes/MWA.itrf.txt +0 -0
  35. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/data/telescopes/MeerKAT.itrf.txt +0 -0
  36. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/data/telescopes/_telescopes.yaml +0 -0
  37. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/jax/__init__.py +0 -0
  38. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/jax/interferometry.py +0 -0
  39. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/jax/utils.py +0 -0
  40. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/plot.py +0 -0
  41. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/scripts/__init__.py +0 -0
  42. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/scripts/ast_region.py +0 -0
  43. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/scripts/flag_data.py +0 -0
  44. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/scripts/sim_vis.py +0 -0
  45. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/scripts/tle_sat_region.py +0 -0
  46. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/sky.py +0 -0
  47. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/tle.py +0 -0
  48. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim/tools.py +0 -0
  49. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim.egg-info/dependency_links.txt +0 -0
  50. {tabsim-0.0.4 → tabsim-0.0.5}/tabsim.egg-info/top_level.txt +0 -0
  51. {tabsim-0.0.4 → tabsim-0.0.5}/tests/test_basic.py +0 -0
  52. {tabsim-0.0.4 → tabsim-0.0.5}/tests/test_sim-vis.py +0 -0
  53. {tabsim-0.0.4 → tabsim-0.0.5}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tabsim
3
- Version: 0.0.4
3
+ Version: 0.0.5
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,11 +680,11 @@ 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.9
683
+ Requires-Python: >=3.9
684
684
  Description-Content-Type: text/markdown
685
685
  License-File: LICENSE
686
686
  Requires-Dist: jax
687
- Requires-Dist: dask<=2024.10.0
687
+ Requires-Dist: dask
688
688
  Requires-Dist: xarray
689
689
  Requires-Dist: zarr<3.0.0
690
690
  Requires-Dist: dask-ms>=0.2.21
@@ -694,7 +694,7 @@ Requires-Dist: matplotlib
694
694
  Requires-Dist: astropy
695
695
  Requires-Dist: spacetrack
696
696
  Requires-Dist: skyfield
697
- Requires-Dist: numpy<2.0.0
697
+ Requires-Dist: numpy
698
698
  Provides-Extra: gpu
699
699
  Requires-Dist: jax[cuda12]; extra == "gpu"
700
700
  Provides-Extra: sat
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "tabsim"
8
- version = "0.0.4"
8
+ version = "0.0.5"
9
9
  license = {file = "LICENSE"}
10
10
  readme = "README.md"
11
11
  # dynamic = ["version", "readme"]
@@ -17,11 +17,11 @@ maintainers = [
17
17
  ]
18
18
  description = "Trajectory-based Radio Frequency Interference (RFI) simulations for radio interferometeric data."
19
19
 
20
- requires-python = ">=3.9,<3.13"
20
+ requires-python = ">=3.9"
21
21
  dependencies = [
22
- "jax", "dask<=2024.10.0", "xarray", "zarr<3.0.0",
22
+ "jax", "dask", "xarray", "zarr<3.0.0",
23
23
  "dask-ms>=0.2.21", "scipy", "tqdm", "matplotlib",
24
- "astropy", "spacetrack", "skyfield", "numpy<2.0.0",
24
+ "astropy", "spacetrack", "skyfield", "numpy",
25
25
  ]
26
26
 
27
27
  [project.optional-dependencies]
@@ -51,3 +51,4 @@ tle-region = "tabsim.scripts.tle_sat_region:main"
51
51
  ast-region = "tabsim.scripts.ast_region:main"
52
52
  flag-data = "tabsim.scripts.flag_data:main"
53
53
  sim-vis = "tabsim.scripts.sim_vis:main"
54
+ write-config = "tabsim.scripts.write_config_from_ms:main"
@@ -18,7 +18,6 @@ from astropy.time import Time
18
18
 
19
19
  from tabsim.dask.observation import Observation
20
20
  from tabsim.sky import generate_random_sky
21
- from tabsim.plot import plot_uv, plot_src_alt, plot_angular_seps
22
21
  from tabsim.write import write_ms, mk_obs_name, mk_obs_dir
23
22
  from tabsim.jax.coordinates import calculate_fringe_frequency, jd_to_mjd
24
23
  from tabsim.tle import get_visible_satellite_tles, id_generator
@@ -256,9 +255,7 @@ def load_obs(obs_spec: dict) -> Observation:
256
255
  start_time_mjd = jd_to_mjd(obs_["start_time_jd"])
257
256
  elif obs_["start_time_isot"]:
258
257
 
259
- start_time_mjd = jd_to_mjd(
260
- Time(obs_["start_time_isot"], format="isot", scale="ut1").jd
261
- )
258
+ start_time_mjd = jd_to_mjd(Time(obs_["start_time_isot"], format="isot").jd)
262
259
  elif obs_["start_time_lha"] is not None:
263
260
  gsa = obs_["start_time_lha"] - tel_["longitude"] + obs_["ra"]
264
261
  start_time_mjd = MJD0 + (gsa / 360)
@@ -668,6 +665,8 @@ def add_gains(obs: Observation, obs_spec: dict) -> None:
668
665
 
669
666
  def plot_diagnostics(obs: Observation, obs_spec: dict, save_path: str) -> None:
670
667
 
668
+ from tabsim.plot import plot_uv, plot_src_alt, plot_angular_seps
669
+
671
670
  diag_ = obs_spec["diagnostics"]
672
671
 
673
672
  if diag_["src_alt"]:
@@ -808,11 +807,14 @@ def print_fringe_freq_sat(obs: Observation):
808
807
  for i in range(obs.n_rfi_satellite)
809
808
  ]
810
809
  fringe_freq = [calculate_fringe_frequency(**f_params) for f_params in fringe_params]
810
+ noise_std = obs.noise_std.mean().compute()
811
+ if noise_std == 0:
812
+ noise_std = 1.0
811
813
  f_sample = (
812
814
  np.pi
813
815
  * np.max(np.abs(fringe_freq))
814
816
  * np.max(obs.rfi_satellite_A_app)
815
- / np.sqrt(6 * obs.noise_std.mean()).compute()
817
+ / np.sqrt(6 * noise_std)
816
818
  )
817
819
  n_int = int(np.ceil(obs.int_time * f_sample))
818
820
 
@@ -843,11 +845,14 @@ def print_fringe_freq_tle_sat(obs: Observation):
843
845
  for i in range(obs.n_rfi_tle_satellite)
844
846
  ]
845
847
  fringe_freq = [calculate_fringe_frequency(**f_params) for f_params in fringe_params]
848
+ noise_std = obs.noise_std.mean().compute()
849
+ if noise_std == 0:
850
+ noise_std = 1.0
846
851
  f_sample = (
847
852
  np.pi
848
853
  * np.max(np.abs(fringe_freq))
849
854
  * np.max(obs.rfi_tle_satellite_A_app)
850
- / np.sqrt(6 * obs.noise_std.mean()).compute()
855
+ / np.sqrt(6 * noise_std)
851
856
  )
852
857
  n_int = int(np.ceil(obs.int_time * f_sample))
853
858
 
@@ -933,9 +938,26 @@ def run_sim_config(
933
938
  print()
934
939
  print(f"Writing data to : {save_path}")
935
940
 
936
- plot_diagnostics(obs, obs_spec, save_path)
941
+ plot_cond = np.array(
942
+ [
943
+ obs_spec["diagnostics"]["rfi_seps"],
944
+ obs_spec["diagnostics"]["src_alt"],
945
+ obs_spec["diagnostics"]["uv_cov"],
946
+ ]
947
+ )
948
+ if np.any(plot_cond):
949
+ plot_diagnostics(obs, obs_spec, save_path)
950
+ else:
951
+ print("\nNo diagnostic plots.")
952
+
937
953
  save_data(obs, obs_spec, zarr_path, ms_path)
938
954
 
955
+ if obs_spec["output"]["accumulate_ms"] is not None:
956
+ from tabsim.write import add_to_ms
957
+
958
+ xds = xr.open_zarr(zarr_path)
959
+ add_to_ms(xds, obs_spec["output"]["accumulate_ms"])
960
+
939
961
  end = datetime.now()
940
962
  print()
941
963
  print(f"Total simulation time : {end - start}")
@@ -947,4 +969,11 @@ def run_sim_config(
947
969
  os.remove(log_path)
948
970
  sys.stdout = backup
949
971
 
950
- return obs, save_path
972
+ if (
973
+ not obs_spec["output"]["keep_sim"]
974
+ and obs_spec["output"]["accumulate_ms"] is not None
975
+ ):
976
+ shutil.rmtree(save_path)
977
+ return obs, save_path
978
+ else:
979
+ return obs, save_path
@@ -290,11 +290,17 @@ class Observation(Telescope):
290
290
  self.n_int_samples // 2, self.n_time_fine, self.n_int_samples
291
291
  ).rechunk(self.time_chunk)
292
292
 
293
+ # self.gsa = da.asarray(
294
+ # Time(self.times_mjd_fine.compute(), format="mjd")
295
+ # .sidereal_time("mean", "greenwich")
296
+ # .hour
297
+ # * 15,
298
+ # chunks=(self.time_fine_chunk,),
299
+ # )
300
+ from tabsim.jax.coordinates import mjd_to_gast
301
+
293
302
  self.gsa = da.asarray(
294
- Time(self.times_mjd_fine.compute(), format="mjd")
295
- .sidereal_time("mean", "greenwich")
296
- .hour
297
- * 15,
303
+ mjd_to_gast(self.times_mjd_fine.compute()),
298
304
  chunks=(self.time_fine_chunk,),
299
305
  )
300
306
  self.gha = (self.gsa - self.ra) % 360
@@ -313,7 +319,9 @@ class Observation(Telescope):
313
319
  self.dish_d = da.asarray(dish_d)
314
320
  self.fov = beam_size(dish_d, freqs.max(), fwhp=False)
315
321
 
316
- self.ants_uvw = ITRF_to_UVW(self.ITRF, self.gha, self.dec)
322
+ self.ants_uvw = da.asarray(
323
+ np.array(ITRF_to_UVW(self.ITRF, self.gha, self.dec).compute())
324
+ )
317
325
 
318
326
  if no_w:
319
327
  self.ants_uvw[:, :, -1] = 0.0
@@ -725,6 +733,11 @@ Number of stationary RFI : {n_stat}"""
725
733
  get_satellite_positions(tles, mjd_to_jd(self.times_mjd_fine.compute())),
726
734
  chunks=(-1, self.time_fine_chunk, 3),
727
735
  )
736
+ # from tabsim.jax.coordinates import kepler_orbit_many
737
+ # from tabsim.tle import
738
+ # rfi_xyz = da.asarray(
739
+ # kepler_orbit_many(mjd_to_jd(self.times_mjd_fine.compute()), )
740
+ # )
728
741
  tles = da.asarray(da.atleast_2d(tles), chunks=(-1,))
729
742
  # rfi_xyz is shape (n_src,n_time_fine,3)
730
743
  # self.ants_xyz is shape (n_time_fine,n_ant,3)
@@ -938,7 +951,7 @@ Number of stationary RFI : {n_stat}"""
938
951
  else:
939
952
  self.flags = (
940
953
  da.abs(self.vis_cal - self.vis_ast)
941
- > 3.0 * da.std(self.vis_model, axis=0)[None, ...]
954
+ > 3.0 * da.std(self.vis_ast, axis=0)[None, ...]
942
955
  )
943
956
  else:
944
957
  self.flags = da.zeros(shape=self.vis_cal.shape, dtype=bool)
@@ -122,6 +122,8 @@ observation:
122
122
  output:
123
123
  zarr: True # Optional - Whether to save the output to a zarr file. This contains almost all inputs ot the simulation
124
124
  ms: True # Optional - Whether to save the output to a Measurement Set file
125
+ accumulate_ms: null # Optional - Whether to add the RFI visibilities to a provided MS file. This should be the MS file path to add to.
126
+ keep_sim: True # Optional - Whether to keep the simulation files generated by tabsim after adding to RFI visibilities to accumulate_ms. This includes all data including generated MS files.
125
127
  flag_data: True # Optional - Whether to apply ideal 3sigma flags
126
128
  overwrite: False # Optional - Whether to overwrite a previous simulation with the same name
127
129
  path: ./ # Optional - Path to where the simulation should be saved
@@ -386,6 +386,61 @@ def jd_from_gmst(gmst: float) -> float:
386
386
  return jd
387
387
 
388
388
 
389
+ def mjd_to_gmst(mjd: float) -> float:
390
+ """Taken from OSKAR simulator"""
391
+
392
+ sec2rad = 7.2722052166430399038487e-5
393
+ two_pi = 6.28318530717958647693
394
+
395
+ # Days since J2000
396
+ jd2000 = mjd - 51544.5
397
+
398
+ # Centuries from J2000.0
399
+ t = jd2000 / 36525.0
400
+
401
+ # GMST at this time
402
+ gmst = (mjd % 1.0) * two_pi + (
403
+ 24110.54841 + (8640184.812866 + (0.093104 - 6.2e-6 * t) * t) * t
404
+ ) * sec2rad
405
+
406
+ # Restrict to 2 pi
407
+ x = (gmst % two_pi) / two_pi * 360
408
+
409
+ return x
410
+
411
+
412
+ def equation_of_equinoxes(mjd: float) -> float:
413
+ """Taken from OSKAR simulator"""
414
+
415
+ deg2rad = 0.0174532925199432957692
416
+ hour2rad = 0.261799387799149436539
417
+ # Days from J2000.0
418
+ jd2000 = mjd - 51544.5
419
+
420
+ # Longitude of ascending node of the Moon
421
+ omega = (125.04 - 0.052954 * jd2000) * deg2rad
422
+
423
+ # Mean Longitude of the Sun
424
+ L = (280.47 + 0.98565 * jd2000) * deg2rad
425
+
426
+ # eqeq = delta_psi * cos(epsilon)
427
+ delta_psi = -0.000319 * jnp.sin(omega) - 0.000024 * jnp.sin(2.0 * L)
428
+ epsilon = (23.4393 - 0.0000004 * jd2000) * deg2rad
429
+
430
+ # Return equation of equinoxes in radians
431
+ eqeq = delta_psi * jnp.cos(epsilon) * hour2rad
432
+
433
+ return eqeq
434
+
435
+
436
+ def mjd_to_gast(mjd: float) -> float:
437
+ """Taken from OSKAR simulator"""
438
+
439
+ gast = mjd_to_gmst(mjd) + equation_of_equinoxes(mjd)
440
+
441
+ return gast
442
+
443
+
389
444
  def jd_to_mjd(jd):
390
445
 
391
446
  mjd = jd - 2400000.5
@@ -0,0 +1,137 @@
1
+ from daskms import xds_from_table, xds_from_ms
2
+ import numpy as np
3
+ from astropy.time import Time
4
+ from astropy.coordinates import EarthLocation
5
+
6
+ from typing import Union
7
+ import os
8
+ import yaml
9
+
10
+
11
+ def write_antenna_itrf_from_ms(
12
+ ms_path: str, tel_name: str = "telescope", save_dir: str = "./"
13
+ ) -> tuple[str, int, float]:
14
+
15
+ xds_ant = xds_from_table(ms_path + "::ANTENNA")[0]
16
+
17
+ itrf = xds_ant.POSITION.data.compute()
18
+ n_ant = len(itrf)
19
+
20
+ dish_d = float(xds_ant.DISH_DIAMETER.data[0].compute())
21
+
22
+ itrf_path = os.path.abspath(os.path.join(save_dir, f"{tel_name}.itrf.txt"))
23
+
24
+ np.savetxt(itrf_path, itrf, fmt="%.6f")
25
+
26
+ return itrf_path, n_ant, dish_d
27
+
28
+
29
+ def read_tel_location_from_ms(ms_path: str) -> tuple[float, float, float]:
30
+
31
+ xds_ant = xds_from_table(ms_path + "::ANTENNA")[0]
32
+
33
+ tel_itrf = xds_ant.POSITION.data.mean(axis=0).compute()
34
+
35
+ tel_loc = EarthLocation(x=tel_itrf[0], y=tel_itrf[1], z=tel_itrf[2], unit="m")
36
+
37
+ lat, lon, elevation = (
38
+ float(tel_loc.lat.deg),
39
+ float(tel_loc.lon.deg),
40
+ float(tel_loc.height.value),
41
+ )
42
+
43
+ return lat, lon, elevation
44
+
45
+
46
+ def read_time_data_from_ms(ms_path: str) -> tuple[str, float, int]:
47
+
48
+ xds_ms = xds_from_ms(ms_path)[0]
49
+
50
+ times = np.unique(xds_ms.TIME.data.compute())
51
+
52
+ n_time = len(times)
53
+ int_time = float(np.diff(np.sort(times)[:2])[0])
54
+
55
+ start_time_isot = Time(np.min(times) / (24 * 3600), format="mjd").isot
56
+
57
+ return start_time_isot, int_time, n_time
58
+
59
+
60
+ def read_freq_data_from_ms(
61
+ ms_path: str,
62
+ ) -> tuple[Union[float, None], Union[float, None], Union[int, None]]:
63
+
64
+ try:
65
+ xds_spec = xds_from_table(ms_path + "::SPECTRAL_WINDOW")[0]
66
+
67
+ freqs = xds_spec.CHAN_FREQ.data[0].compute()
68
+ start_freq = float(freqs[0])
69
+ n_freq = len(freqs)
70
+
71
+ chan_width = float(xds_spec.CHAN_WIDTH.data[0, 0].compute())
72
+ except:
73
+ print("Frequency information could not be red from MS file.")
74
+ start_freq, chan_width, n_freq = None, None, None
75
+
76
+ return start_freq, chan_width, n_freq
77
+
78
+
79
+ def read_phase_centre_from_ms(
80
+ ms_path: str,
81
+ ) -> tuple[Union[float, None], Union[float, None]]:
82
+
83
+ try:
84
+ xds_src = xds_from_table(ms_path + "::SOURCE")[0]
85
+
86
+ ra, dec = [float(x) for x in np.rad2deg(xds_src.DIRECTION.data[0]).compute()]
87
+ except:
88
+ print("Phase centre could not be read from MS file.")
89
+ ra, dec = None, None
90
+
91
+ return ra, dec
92
+
93
+
94
+ def construct_config_from_ms(
95
+ ms_path: str, tel_name: str = "telescope", save_dir: str = "./"
96
+ ) -> tuple[str, dict]:
97
+
98
+ itrf_path, n_ant, dish_d = write_antenna_itrf_from_ms(ms_path, tel_name, save_dir)
99
+ lat, lon, elevation = read_tel_location_from_ms(ms_path)
100
+ ra, dec = read_phase_centre_from_ms(ms_path)
101
+ start_time_isot, int_time, n_time = read_time_data_from_ms(ms_path)
102
+ start_freq, chan_width, n_freq = read_freq_data_from_ms(ms_path)
103
+
104
+ config = {
105
+ "telescope": {
106
+ "name": tel_name,
107
+ "latitude": lat,
108
+ "longitude": lon,
109
+ "elevation": elevation,
110
+ "itrf_path": itrf_path,
111
+ "n_ant": n_ant,
112
+ # "dish_d": dish_d,
113
+ },
114
+ "observation": {
115
+ "start_time_isot": start_time_isot,
116
+ "int_time": int_time,
117
+ "n_time": n_time,
118
+ "start_freq": start_freq,
119
+ "chan_width": chan_width,
120
+ "n_freq": n_freq,
121
+ },
122
+ }
123
+
124
+ if ra is not None:
125
+ config["observation"].update(
126
+ {
127
+ "target_name": f"pointing_{ra:.3f}_{dec:.3f}",
128
+ "ra": ra,
129
+ "dec": dec,
130
+ }
131
+ )
132
+
133
+ config_path = os.path.abspath(os.path.join(save_dir, f"{tel_name}_sim_config.yaml"))
134
+
135
+ yaml.dump(config, open(config_path, "w"))
136
+
137
+ return config_path, config
@@ -0,0 +1,40 @@
1
+ from tabsim.read_ms import construct_config_from_ms
2
+
3
+
4
+ def main():
5
+
6
+ import argparse
7
+
8
+ parser = argparse.ArgumentParser(
9
+ description="Extract simulation configuration parameters from an MS file and write to disk."
10
+ )
11
+ parser.add_argument(
12
+ "-ms",
13
+ "--ms_path",
14
+ required=True,
15
+ help="File path to the MS file.",
16
+ )
17
+ parser.add_argument(
18
+ "-t",
19
+ "--tel_name",
20
+ default="telescope",
21
+ help="Telescope name.",
22
+ )
23
+ parser.add_argument(
24
+ "-s",
25
+ "--save_dir",
26
+ default="./",
27
+ help="Path to directory where config and antenna coordinates should be saved.",
28
+ )
29
+ args = parser.parse_args()
30
+
31
+ config_path, config = construct_config_from_ms(
32
+ args.ms_path, args.tel_name, args.save_dir
33
+ )
34
+
35
+ print(f"Simulation configuration file save to : {config_path}")
36
+
37
+
38
+ if __name__ == "__main__":
39
+
40
+ main()
@@ -383,7 +383,37 @@ def get_stationary_rfi_data(obs: Observation):
383
383
  return rfi_stat
384
384
 
385
385
 
386
- def add_to_ms(ds: Dataset, ms_path: str, flags: Optional[Array] = None):
386
+ def construct_rfi_mscol(vis_rfi: Array, data: Array):
387
+
388
+ n_corr = data.shape[-1]
389
+
390
+ if n_corr == 1:
391
+ vis_rfi_ms = xr.DataArray(
392
+ vis_rfi[:, :, None],
393
+ dims=("row", "chan", "corr"),
394
+ )
395
+ elif n_corr == 2:
396
+ vis_rfi_ms = xr.DataArray(
397
+ da.stack([vis_rfi, vis_rfi], axis=2),
398
+ dims=("row", "chan", "corr"),
399
+ )
400
+ elif n_corr == 4:
401
+ vis_rfi_ms = xr.DataArray(
402
+ da.stack(
403
+ [vis_rfi, da.zeros_like(vis_rfi), da.zeros_like(vis_rfi), vis_rfi],
404
+ axis=2,
405
+ ),
406
+ dims=("row", "chan", "corr"),
407
+ )
408
+ else:
409
+ raise ValueError("Correlation axis in target MS file is non-standard")
410
+
411
+ return vis_rfi_ms
412
+
413
+
414
+ def add_to_ms(
415
+ ds: xr.Dataset, ms_path: str, flags: Optional[Array] = None, data_col: str = "DATA"
416
+ ):
387
417
 
388
418
  n_time = ds.attrs["n_time"]
389
419
  n_freq = ds.attrs["n_freq"]
@@ -396,24 +426,27 @@ def add_to_ms(ds: Dataset, ms_path: str, flags: Optional[Array] = None):
396
426
  dims = ["row", "chan", "corr"]
397
427
  chunks = {k: v for k, v in xds_ms.chunks.items() if k in dims}
398
428
 
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"),
429
+ # vis_rfi_ms = construct_rfi_mscol(
430
+ # ds.vis_rfi.data.reshape(n_row, n_freq), xds_ms[data_col]
431
+ # )
432
+ vis_rfi_ms = construct_rfi_mscol(
433
+ np.conjugate(ds.vis_rfi.data.reshape(n_row, n_freq)), xds_ms[data_col]
434
+ ) # Needed for OSKAR simulations compatibility
435
+
436
+ xds_ms = xds_ms.assign({data_col: xds_ms[data_col] + vis_rfi_ms.chunk(chunks)})
437
+ cols = data_col
438
+
439
+ # if flags:
440
+ # flags = xr.DataArray(
441
+ # ds.flags.data.reshape(n_row, n_freq, n_corr), dims=("row", "chan", "corr")
442
+ # )
443
+ # xds_ms = xds_ms.assign(FLAG=flags)
444
+ # cols.append("FLAG")
445
+
446
+ print(
447
+ f"Writing tabsim generated RFI visibilities to {data_col} column in {ms_path}"
405
448
  )
406
449
 
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
450
  dask.compute(xds_to_table([xds_ms], ms_path, cols))
418
451
 
419
452
 
@@ -479,6 +512,7 @@ def construct_ms_data_table(
479
512
 
480
513
  row_times = da.asarray(
481
514
  (ds.coords["time_mjd"].data[:, None] * da.ones(shape=(1, n_bl))).flatten()
515
+ * (24 * 3600)
482
516
  )
483
517
  ant1 = (
484
518
  (ds.antenna1.data[None, :] * da.ones(shape=(n_time, 1)))
@@ -515,10 +549,10 @@ def construct_ms_data_table(
515
549
  "ARRAY_ID": (("row"), a_id),
516
550
  "FLAG": (("row", "chan", "corr"), flags),
517
551
  "INTERVAL": (("row"), interval),
518
- "FLAG_CATEGORY": (
519
- ("row", "chan", "corr", "flagcat"),
520
- da.expand_dims(flags, axis=3),
521
- ),
552
+ # "FLAG_CATEGORY": (
553
+ # ("row", "chan", "corr", "flagcat"),
554
+ # da.expand_dims(flags, axis=3),
555
+ # ),
522
556
  }
523
557
 
524
558
  if extras:
@@ -677,7 +711,7 @@ def construct_ms_spectral_window_table(ds: Dataset, ms_path: str):
677
711
  "CHAN_FREQ": (("row", "chan"), chan_freq),
678
712
  "CHAN_WIDTH": (("row", "chan"), chan_width),
679
713
  "EFFECTIVE_BW": (("row", "chan"), chan_width),
680
- "EFFECTIVE_BW": (("row", "chan"), n_freq * chan_width),
714
+ # "EFFECTIVE_BW": (("row", "chan"), n_freq * chan_width),
681
715
  "FLAG_ROW": (("row"), zero),
682
716
  "FREQ_GROUP": (("row"), zero),
683
717
  # 'FREQ_GROUP_NAME': (('row'), zero),
@@ -685,7 +719,7 @@ def construct_ms_spectral_window_table(ds: Dataset, ms_path: str):
685
719
  "MEAS_FREQ_REF": (("row"), 5 * one),
686
720
  # 'NAME': (('row'), zero),
687
721
  "NET_SIDEBAND": (("row"), one),
688
- "NUM_CHAN": (("row"), one),
722
+ "NUM_CHAN": (("row"), n_freq * one),
689
723
  "REF_FREQUENCY": (("row"), ref_freq),
690
724
  "RESOLUTION": (("row", "chan"), chan_width),
691
725
  "TOTAL_BANDWIDTH": (("row"), total_bw),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tabsim
3
- Version: 0.0.4
3
+ Version: 0.0.5
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,11 +680,11 @@ 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.9
683
+ Requires-Python: >=3.9
684
684
  Description-Content-Type: text/markdown
685
685
  License-File: LICENSE
686
686
  Requires-Dist: jax
687
- Requires-Dist: dask<=2024.10.0
687
+ Requires-Dist: dask
688
688
  Requires-Dist: xarray
689
689
  Requires-Dist: zarr<3.0.0
690
690
  Requires-Dist: dask-ms>=0.2.21
@@ -694,7 +694,7 @@ Requires-Dist: matplotlib
694
694
  Requires-Dist: astropy
695
695
  Requires-Dist: spacetrack
696
696
  Requires-Dist: skyfield
697
- Requires-Dist: numpy<2.0.0
697
+ Requires-Dist: numpy
698
698
  Provides-Extra: gpu
699
699
  Requires-Dist: jax[cuda12]; extra == "gpu"
700
700
  Provides-Extra: sat
@@ -5,6 +5,7 @@ docs/conf.py
5
5
  tabsim/__init__.py
6
6
  tabsim/config.py
7
7
  tabsim/plot.py
8
+ tabsim/read_ms.py
8
9
  tabsim/sky.py
9
10
  tabsim/tle.py
10
11
  tabsim/tools.py
@@ -44,6 +45,7 @@ tabsim/scripts/ast_region.py
44
45
  tabsim/scripts/flag_data.py
45
46
  tabsim/scripts/sim_vis.py
46
47
  tabsim/scripts/tle_sat_region.py
48
+ tabsim/scripts/write_config_from_ms.py
47
49
  tests/test_basic.py
48
50
  tests/test_sim-vis.py
49
51
  tests/test_version.py
@@ -3,3 +3,4 @@ ast-region = tabsim.scripts.ast_region:main
3
3
  flag-data = tabsim.scripts.flag_data:main
4
4
  sim-vis = tabsim.scripts.sim_vis:main
5
5
  tle-region = tabsim.scripts.tle_sat_region:main
6
+ write-config = tabsim.scripts.write_config_from_ms:main
@@ -1,5 +1,5 @@
1
1
  jax
2
- dask<=2024.10.0
2
+ dask
3
3
  xarray
4
4
  zarr<3.0.0
5
5
  dask-ms>=0.2.21
@@ -9,7 +9,7 @@ matplotlib
9
9
  astropy
10
10
  spacetrack
11
11
  skyfield
12
- numpy<2.0.0
12
+ numpy
13
13
 
14
14
  [docs]
15
15
  sphinx
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
File without changes
File without changes
File without changes