From 12c5997ee52d7e5659a5e6399c51bc2969495095 Mon Sep 17 00:00:00 2001 From: Kevin Schmidt Date: Sat, 2 May 2026 16:26:57 +0000 Subject: [PATCH 1/9] save channel bandwidths in UVH5Writer --- src/pyvisgen/io/datawriters.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pyvisgen/io/datawriters.py b/src/pyvisgen/io/datawriters.py index 016a7b70..96fd175a 100644 --- a/src/pyvisgen/io/datawriters.py +++ b/src/pyvisgen/io/datawriters.py @@ -422,6 +422,7 @@ class UVH5Writer(DataWriter): │ ├── m │ └── n ├── frequency_bands + ├── channel_widths └── sky/ └── SI @@ -523,6 +524,7 @@ def write( freq_bands = self.__to_numpy(obs.ref_frequency + obs.frequency_offsets) f.create_dataset("frequency_bands", data=freq_bands) + f.create_dataset("channel_widths", data=self.__to_numpy(obs.bandwidths)) if sky is not None: sky_grp = f.create_group("sky") From 20679c8d2cdfddcb65fbd58bc30c9792d5bc7fef Mon Sep 17 00:00:00 2001 From: Kevin Schmidt Date: Sat, 2 May 2026 16:29:42 +0000 Subject: [PATCH 2/9] add changelog fragment --- docs/changes/150.optimiyation.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/changes/150.optimiyation.rst diff --git a/docs/changes/150.optimiyation.rst b/docs/changes/150.optimiyation.rst new file mode 100644 index 00000000..83b127cd --- /dev/null +++ b/docs/changes/150.optimiyation.rst @@ -0,0 +1 @@ +add channel bandwidths to UVH5Writer From e6c615f4b6bde12e845f865511da31053e7c8dc2 Mon Sep 17 00:00:00 2001 From: Kevin Schmidt Date: Thu, 7 May 2026 16:25:30 +0000 Subject: [PATCH 3/9] save visibility normalization info to uvh5 files --- src/pyvisgen/dataset/dataset.py | 7 ++++++- src/pyvisgen/io/datawriters.py | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pyvisgen/dataset/dataset.py b/src/pyvisgen/dataset/dataset.py index 3f610ec4..fa2ca658 100644 --- a/src/pyvisgen/dataset/dataset.py +++ b/src/pyvisgen/dataset/dataset.py @@ -268,7 +268,12 @@ def _run(self) -> None: else: for j, vis_data in enumerate(sim_data): self.writer.write( - vis_data, obs, index=i, sky=SIs[j], overwrite=True + vis_data, + obs, + index=i, + sky=SIs[j], + overwrite=True, + normalize=self.conf.sampling.normalize, ) if fits_writer is not None: diff --git a/src/pyvisgen/io/datawriters.py b/src/pyvisgen/io/datawriters.py index 96fd175a..548676b1 100644 --- a/src/pyvisgen/io/datawriters.py +++ b/src/pyvisgen/io/datawriters.py @@ -467,6 +467,7 @@ def write( obs, index: int, sky=None, + normalize: bool = True, **kwargs, ) -> None: """Write simulation data to an HDF5 file. @@ -525,6 +526,7 @@ def write( freq_bands = self.__to_numpy(obs.ref_frequency + obs.frequency_offsets) f.create_dataset("frequency_bands", data=freq_bands) f.create_dataset("channel_widths", data=self.__to_numpy(obs.bandwidths)) + f.create_dataset("normalize", data=np.bool_(normalize)) if sky is not None: sky_grp = f.create_group("sky") From 1ae4064017849f23139a57979111b5982c52d158 Mon Sep 17 00:00:00 2001 From: Kevin Schmidt Date: Thu, 7 May 2026 16:26:36 +0000 Subject: [PATCH 4/9] fix np typing problem --- src/pyvisgen/simulation/observation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyvisgen/simulation/observation.py b/src/pyvisgen/simulation/observation.py index ee5c28cf..fad5d829 100644 --- a/src/pyvisgen/simulation/observation.py +++ b/src/pyvisgen/simulation/observation.py @@ -3,6 +3,7 @@ import astropy.units as un import numpy as np +import numpy.typing import torch from astropy.constants import c from astropy.coordinates import AltAz, EarthLocation, Longitude, SkyCoord From e9d39688ca7e4fdfb1cc5a094b1b0e8b226b83bf Mon Sep 17 00:00:00 2001 From: Kevin Schmidt Date: Thu, 7 May 2026 16:35:02 +0000 Subject: [PATCH 5/9] fix tests --- tests/io/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/io/conftest.py b/tests/io/conftest.py index 440fd449..6801f7d8 100644 --- a/tests/io/conftest.py +++ b/tests/io/conftest.py @@ -191,6 +191,7 @@ def uvh5_obs() -> SimpleNamespace: lm=lm, ref_frequency=torch.tensor(15.7e9), frequency_offsets=torch.tensor([0.0, 1.0e6]), + bandwidths=torch.tensor([1.0e6, 1.0e6]), ) From ea33c924a3a96e5c8bb2b16be8399ae2f1bf9dbb Mon Sep 17 00:00:00 2001 From: Kevin Schmidt Date: Tue, 12 May 2026 08:56:59 +0000 Subject: [PATCH 6/9] fix tests --- tests/dataset/test_dataset.py | 42 +++++++++++++++++++++++++++++++++++ tests/io/test_config.py | 16 +++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/tests/dataset/test_dataset.py b/tests/dataset/test_dataset.py index 33a7ca5d..89f698f9 100644 --- a/tests/dataset/test_dataset.py +++ b/tests/dataset/test_dataset.py @@ -7,6 +7,7 @@ import torch from pyvisgen.dataset import SimulateDataSet +from pyvisgen.dataset.dataset import DATEFMT from pyvisgen.io import Config from pyvisgen.layouts import Stations @@ -238,6 +239,47 @@ def test_polarization(self, pol_mode: str, sd_sampling: SimulateDataSet) -> None assert samp_opts["order"].shape == (size, 2) assert samp_opts["scale"].shape == (size, 2) + @pytest.mark.parametrize( + "field,value", + [ + ("fov_center_ra", [-173.867]), + ("fov_center_dec", [6.474]), + ("scan_duration", [272]), + ("num_scans", [9]), + ], + ) + def test_fixed_scalar_fields( + self, field: str, value: list, sd_sampling: SimulateDataSet + ) -> None: + """Single-value list bypasses random draw and repeats the fixed value.""" + setattr(sd_sampling.conf.sampling, field, value) + + size = 5 + samp_opts = sd_sampling.draw_sampling_opts(size) + + key_map = { + "fov_center_ra": "src_ra", + "fov_center_dec": "src_dec", + "scan_duration": "scan_duration", + "num_scans": "num_scans", + } + result = samp_opts[key_map[field]] + assert result.shape == (size,) + assert (result == value[0]).all() + + def test_fixed_scan_start(self, sd_sampling: SimulateDataSet) -> None: + """Single-value scan_start bypasses random draw and + repeats the fixed datetime.""" + date_str = "22-04-2023 17:21:11" + sd_sampling.conf.sampling.scan_start = [date_str] + + size = 5 + samp_opts = sd_sampling.draw_sampling_opts(size) + + expected = datetime.strptime(date_str, DATEFMT) + assert samp_opts["start_time"].shape == (size,) + assert all(t == expected for t in samp_opts["start_time"]) + def test_polarization_kwargs_none(self, sd_sampling: SimulateDataSet) -> None: sd_sampling.conf.polarization.mode = "linear" sd_sampling.conf.polarization.delta = None diff --git a/tests/io/test_config.py b/tests/io/test_config.py index 718226b9..e83ad3d0 100644 --- a/tests/io/test_config.py +++ b/tests/io/test_config.py @@ -273,11 +273,23 @@ def test_validate_dates(self) -> None: assert cfg.scan_start == dates + def test_validate_dates_single(self) -> None: + dates = ["2024-06-15 10:00:00"] + cfg = SamplingConfig(scan_start=dates) + + assert cfg.scan_start == dates + def test_validate_dates_invalid(self) -> None: with pytest.raises(ValueError) as excinfo: - SamplingConfig(scan_start=["2025-01-01 12:00:00"]) + SamplingConfig( + scan_start=[ + "2024-01-01 12:00:00", + "2025-01-01 12:00:00", + "2026-01-01 12:00:00", + ] + ) - assert "expected 'scan_start' to be a list of len 2" in str(excinfo.value) + assert "expected 'scan_start' to be a list of len 1 or 2" in str(excinfo.value) @pytest.mark.parametrize( "seed,expected", [(42, 42), (None, None), ("none", None), (False, None)] From a66969f1ae03536653fbf77257f39781431fc673 Mon Sep 17 00:00:00 2001 From: Kevin Schmidt Date: Tue, 12 May 2026 08:57:13 +0000 Subject: [PATCH 7/9] add missing antennas --- resources/layouts/meerkat.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/resources/layouts/meerkat.txt b/resources/layouts/meerkat.txt index 95f0119e..e27703f5 100644 --- a/resources/layouts/meerkat.txt +++ b/resources/layouts/meerkat.txt @@ -58,3 +58,7 @@ m060 5107254.013471882 2009699.3572179652 -3240542.587340528 13.5 15.0 85.0 110. m061 5108278.559161539 2006410.136906058 -3240956.885313587 13.5 15.0 85.0 110.0 1000.0 m062 5108713.98241022 2005051.0165491276 -3241111.829132157 13.5 15.0 85.0 110.0 1000.0 m063 5109748.526320712 2003331.232038675 -3240538.853735712 13.5 15.0 85.0 110.0 1000.0 +m008 5109148.3563610995 2006668.9215486543 -3239413.4521834562 13.5 15.0 85.0 110.0 1000.0 +m021 5109319.2750222068 2006518.5606004531 -3239233.6195771112 13.5 15.0 85.0 110.0 1000.0 +m022 5109501.2780305194 2006507.2823303600 -3238950.5447669746 13.5 15.0 85.0 110.0 1000.0 +m023 5109415.8381663673 2006528.1891329922 -3239073.8564673522 13.5 15.0 85.0 110.0 1000.0 From 9a208ca0ee8f5cec4d7aaa9b2a4b64ef60436140 Mon Sep 17 00:00:00 2001 From: Kevin Schmidt Date: Tue, 12 May 2026 08:57:42 +0000 Subject: [PATCH 8/9] allow single arguments in simconfig --- src/pyvisgen/dataset/dataset.py | 67 +++++++++++++++++---------------- src/pyvisgen/io/config.py | 4 +- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/pyvisgen/dataset/dataset.py b/src/pyvisgen/dataset/dataset.py index fa2ca658..6fd27c26 100644 --- a/src/pyvisgen/dataset/dataset.py +++ b/src/pyvisgen/dataset/dataset.py @@ -473,46 +473,49 @@ def draw_sampling_opts(self, size: int) -> dict: samp_opts : dict Sampling options/parameters stored inside a dictionary. """ - ra = self.rng.uniform( - self.conf.sampling.fov_center_ra[0], - self.conf.sampling.fov_center_ra[1], - size, + ra_cfg = self.conf.sampling.fov_center_ra + ra = ( + np.full(size, ra_cfg[0]) + if len(ra_cfg) == 1 + else self.rng.uniform(ra_cfg[0], ra_cfg[1], size) ) - dec = self.rng.uniform( - self.conf.sampling.fov_center_dec[0], - self.conf.sampling.fov_center_dec[1], - size, + + dec_cfg = self.conf.sampling.fov_center_dec + dec = ( + np.full(size, dec_cfg[0]) + if len(dec_cfg) == 1 + else self.rng.uniform(dec_cfg[0], dec_cfg[1], size) ) start_time_l = datetime.strptime( self.conf.sampling.scan_start[0], self.date_fmt ) - start_time_h = datetime.strptime( - self.conf.sampling.scan_start[1], self.date_fmt - ) - start_times = np.arange( - start_time_l, - start_time_h, - timedelta(hours=1), - ).astype(datetime) - - scan_start = self.rng.choice(start_times, size) - scan_duration = self.rng.integers( - self.conf.sampling.scan_duration[0], - self.conf.sampling.scan_duration[1], - size, - ) - num_scans = self.rng.integers( - self.conf.sampling.num_scans[0], - self.conf.sampling.num_scans[1], - size, + if len(self.conf.sampling.scan_start) == 1: + scan_start = np.full(size, start_time_l) + else: + start_time_h = datetime.strptime( + self.conf.sampling.scan_start[1], self.date_fmt + ) + start_times = np.arange( + start_time_l, + start_time_h, + timedelta(hours=1), + ).astype(datetime) + scan_start = self.rng.choice(start_times, size) + + dur_cfg = self.conf.sampling.scan_duration + scan_duration = ( + np.full(size, dur_cfg[0], dtype=int) + if len(dur_cfg) == 1 + else self.rng.integers(dur_cfg[0], dur_cfg[1], size) ) - if scan_duration.size == 1: - scan_duration = scan_duration.astype(int) - - if num_scans.size == 1: - num_scans = num_scans.astype(int) + ns_cfg = self.conf.sampling.num_scans + num_scans = ( + np.full(size, ns_cfg[0], dtype=int) + if len(ns_cfg) == 1 + else self.rng.integers(ns_cfg[0], ns_cfg[1], size) + ) # if polarization is None, we don't need to enter the # conditional below, so we set delta, amp_ratio, field_order, diff --git a/src/pyvisgen/io/config.py b/src/pyvisgen/io/config.py index bd924005..8bd9b3b5 100644 --- a/src/pyvisgen/io/config.py +++ b/src/pyvisgen/io/config.py @@ -56,8 +56,8 @@ def validate_layout(cls, layout: str) -> None: @field_validator("scan_start") @classmethod def validate_dates(cls, v: list[str]) -> None: - if len(v) != 2: - raise ValueError("expected 'scan_start' to be a list of len 2") + if len(v) not in (1, 2): + raise ValueError("expected 'scan_start' to be a list of len 1 or 2") return v From 1fa28f2a4fbdfa32f7cbb7df088bb95b915e09b6 Mon Sep 17 00:00:00 2001 From: Kevin Schmidt Date: Tue, 12 May 2026 09:01:28 +0000 Subject: [PATCH 9/9] update fragments --- docs/changes/150.bugfix.rst | 1 + docs/changes/150.feature.rst | 1 + docs/changes/{150.optimiyation.rst => 150.optimization.rst} | 0 3 files changed, 2 insertions(+) create mode 100644 docs/changes/150.bugfix.rst create mode 100644 docs/changes/150.feature.rst rename docs/changes/{150.optimiyation.rst => 150.optimization.rst} (100%) diff --git a/docs/changes/150.bugfix.rst b/docs/changes/150.bugfix.rst new file mode 100644 index 00000000..76a5b580 --- /dev/null +++ b/docs/changes/150.bugfix.rst @@ -0,0 +1 @@ +add four missing antennas for MeerKat layout diff --git a/docs/changes/150.feature.rst b/docs/changes/150.feature.rst new file mode 100644 index 00000000..840fddc2 --- /dev/null +++ b/docs/changes/150.feature.rst @@ -0,0 +1 @@ +allow sinlge values for simulation config (bypass random value drawing) diff --git a/docs/changes/150.optimiyation.rst b/docs/changes/150.optimization.rst similarity index 100% rename from docs/changes/150.optimiyation.rst rename to docs/changes/150.optimization.rst