From 8dd9f9000f882133e91cd3088a4dda64911500eb Mon Sep 17 00:00:00 2001 From: Steven Murray Date: Tue, 28 Nov 2023 10:52:31 +0100 Subject: [PATCH 1/5] refactor: use matvis insteaf of vis_cpu --- .pre-commit-config.yaml | 16 +- hera_sim/tests/test_beams.py | 4 +- hera_sim/tests/test_compare_pyuvsim.py | 16 +- hera_sim/tests/test_vis.py | 38 +- hera_sim/tests/test_vis_cli.py | 6 +- .../{viscpu.yaml => matvis_cpu.yaml} | 2 +- .../{visgpu.yaml => matvis_gpu.yaml} | 2 +- hera_sim/visibilities/__init__.py | 2 +- hera_sim/visibilities/matvis.py | 517 +++++++++++++++++ hera_sim/visibilities/vis_cpu.py | 519 ------------------ 10 files changed, 561 insertions(+), 561 deletions(-) rename hera_sim/tests/testdata/hera-sim-vis-config/{viscpu.yaml => matvis_cpu.yaml} (83%) rename hera_sim/tests/testdata/hera-sim-vis-config/{visgpu.yaml => matvis_gpu.yaml} (85%) create mode 100644 hera_sim/visibilities/matvis.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c4b4c033..a39a21ee 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,14 +32,16 @@ repos: - flake8-bugbear - flake8-comprehensions - flake8-print -- repo: https://github.com/psf/black - rev: 23.11.0 - hooks: + +- repo: https://github.com/psf/black-pre-commit-mirror + rev: 23.11.0 + hooks: - id: black -- repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.10.0 - hooks: - - id: rst-backticks + +- repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 + hooks: + - id: rst-backticks - repo: https://github.com/PyCQA/isort rev: 5.12.0 diff --git a/hera_sim/tests/test_beams.py b/hera_sim/tests/test_beams.py index 0a6cac6b..e0dcd111 100644 --- a/hera_sim/tests/test_beams.py +++ b/hera_sim/tests/test_beams.py @@ -16,7 +16,7 @@ stokes_matrix, ) from hera_sim.defaults import defaults -from hera_sim.visibilities import ModelData, VisCPU, VisibilitySimulation +from hera_sim.visibilities import MatVis, ModelData, VisibilitySimulation np.seterr(invalid="ignore") @@ -118,7 +118,7 @@ def run_sim( # calculate source fluxes for hera_sim flux = (freqs[:, np.newaxis] / freqs[0]) ** spectral_index * flux - simulator = VisCPU( + simulator = MatVis( use_gpu=use_gpu, mpi_comm=DummyMPIComm() if use_mpi else None, precision=2, diff --git a/hera_sim/tests/test_compare_pyuvsim.py b/hera_sim/tests/test_compare_pyuvsim.py index cf6d5170..c3bceb08 100644 --- a/hera_sim/tests/test_compare_pyuvsim.py +++ b/hera_sim/tests/test_compare_pyuvsim.py @@ -13,7 +13,7 @@ from hera_sim import io from hera_sim.beams import PolyBeam -from hera_sim.visibilities import ModelData, VisCPU, VisibilitySimulation +from hera_sim.visibilities import MatVis, ModelData, VisibilitySimulation nfreq = 3 ntime = 20 @@ -150,7 +150,7 @@ def get_beams(beam_type, polarized): (100, "PolyBeam", True), ], ) -def test_compare_viscpu_with_pyuvsim(uvdata_allpols, nsource, beam_type, polarized): +def test_compare_matvis_with_pyuvsim(uvdata_allpols, nsource, beam_type, polarized): """Compare vis_cpu and pyuvsim simulated visibilities.""" sky_model = get_sky_model(uvdata_allpols, nsource) @@ -162,13 +162,13 @@ def test_compare_viscpu_with_pyuvsim(uvdata_allpols, nsource, beam_type, polariz # (1) Run vis_cpu # --------------------------------------------------------------------------- # Trim unwanted polarizations - uvdata_viscpu = copy.deepcopy(uvdata_allpols) + uvdata_matvis = copy.deepcopy(uvdata_allpols) if not polarized: - uvdata_viscpu.select(polarizations=["ee"], inplace=True) + uvdata_matvis.select(polarizations=["ee"], inplace=True) # Construct simulator object and run - simulator = VisCPU( + simulator = MatVis( ref_time=Time("2018-08-31T04:02:30.11", format="isot", scale="utc"), use_gpu=False, ) @@ -182,13 +182,13 @@ def test_compare_viscpu_with_pyuvsim(uvdata_allpols, nsource, beam_type, polariz sim = VisibilitySimulation( data_model=ModelData( - uvdata=uvdata_viscpu, sky_model=sky_model, beams=vis_cpu_beams + uvdata=uvdata_matvis, sky_model=sky_model, beams=vis_cpu_beams ), simulator=simulator, ) sim.simulate() - uvd_viscpu = sim.uvdata + uvd_matvis = sim.uvdata # --------------------------------------------------------------------------- # (2) Run pyuvsim @@ -220,7 +220,7 @@ def test_compare_viscpu_with_pyuvsim(uvdata_allpols, nsource, beam_type, polariz print("Baseline: ", i, j) np.testing.assert_allclose( uvd_uvsim.get_data((i, j, "xx")), - uvd_viscpu.get_data((i, j, "xx")), + uvd_matvis.get_data((i, j, "xx")), atol=atol, rtol=rtol, ) diff --git a/hera_sim/tests/test_vis.py b/hera_sim/tests/test_vis.py index 0a5c2954..2dad4522 100644 --- a/hera_sim/tests/test_vis.py +++ b/hera_sim/tests/test_vis.py @@ -16,20 +16,20 @@ from hera_sim.beams import PolyBeam from hera_sim.defaults import defaults from hera_sim.visibilities import ( + MatVis, ModelData, UVSim, - VisCPU, VisibilitySimulation, load_simulator_from_yaml, ) from vis_cpu import HAVE_GPU -SIMULATORS = (VisCPU, UVSim) +SIMULATORS = (MatVis, UVSim) if HAVE_GPU: - class VisGPU(VisCPU): - """Simple mock class to make testing VisCPU with use_gpu=True easier""" + class VisGPU(MatVis): + """Simple mock class to make testing MatVis with use_gpu=True easier""" def __init__(self, *args, **kwargs): super().__init__(*args, use_gpu=True, ref_time="min", **kwargs) @@ -111,7 +111,7 @@ def sky_modelJD(uvdataJD): def test_JD(uvdata, uvdataJD, sky_model): model_data = ModelData(sky_model=sky_model, uvdata=uvdata) - vis = VisCPU() + vis = MatVis() sim1 = VisibilitySimulation(data_model=model_data, simulator=vis).simulate() @@ -125,7 +125,7 @@ def test_JD(uvdata, uvdataJD, sky_model): def test_vis_cpu_estimate_memory(uvdata, uvdataJD, sky_model): model_data = ModelData(sky_model=sky_model, uvdata=uvdata) - vis = VisCPU() + vis = MatVis() mem = vis.estimate_memory(model_data) assert mem > 0 @@ -262,7 +262,7 @@ def test_shapes(uvdata, simulator): @pytest.mark.parametrize("precision, cdtype", [(1, np.complex64), (2, complex)]) def test_dtypes(uvdata, precision, cdtype): sky = create_uniform_sky(np.unique(uvdata.freq_array)) - vis = VisCPU(precision=precision) + vis = MatVis(precision=precision) # If data_array is empty, then we never create new vis, and the returned value # is literally the data array, so we should expect to get complex128 regardless. @@ -356,19 +356,19 @@ def test_single_source_autocorr_past_horizon(uvdata, simulator): assert np.abs(np.mean(v)) == 0 -def test_viscpu_coordinate_correction(uvdata2): +def test_matvis_coordinate_correction(uvdata2): sim = VisibilitySimulation( data_model=ModelData( uvdata=uvdata2, sky_model=zenith_sky_model(uvdata2), ), - simulator=VisCPU( + simulator=MatVis( correct_source_positions=True, ref_time="2018-08-31T04:02:30.11" ), ) # Apply correction - # viscpu.correct_point_source_pos(obstime="2018-08-31T04:02:30.11", frame="icrs") + # matvis.correct_point_source_pos(obstime="2018-08-31T04:02:30.11", frame="icrs") v = sim.simulate().copy() assert np.all(~np.isnan(v)) @@ -377,7 +377,7 @@ def test_viscpu_coordinate_correction(uvdata2): uvdata=uvdata2, sky_model=zenith_sky_model(uvdata2), ), - simulator=VisCPU( + simulator=MatVis( correct_source_positions=True, ref_time=apt.Time("2018-08-31T04:02:30.11", format="isot", scale="utc"), ), @@ -526,7 +526,7 @@ def test_vis_cpu_pol(polarization_array, xfail): ) beam = PolyBeam(polarized=False) - simulator = VisCPU() + simulator = MatVis() if xfail: with pytest.raises(KeyError): @@ -567,7 +567,7 @@ def test_vis_cpu_stokespol(uvdata_linear, sky_model): with pytest.raises(ValueError): VisibilitySimulation( data_model=ModelData(uvdata=uvdata_linear, sky_model=sky_model), - simulator=VisCPU(), + simulator=MatVis(), ) @@ -585,10 +585,10 @@ def test_str_uvdata(uvdata, sky_model, tmp_path): assert model_data.uvdata.Nants_data == uvdata.Nants_data -def test_ref_time_viscpu(uvdata2): - vc_mean = VisCPU(ref_time="mean") - vc_min = VisCPU(ref_time="min") - vc_max = VisCPU(ref_time="max") +def test_ref_time_matvis(uvdata2): + vc_mean = MatVis(ref_time="mean") + vc_min = MatVis(ref_time="min") + vc_max = MatVis(ref_time="max") sky_model = half_sky_model(uvdata2) @@ -615,10 +615,10 @@ def test_load_from_yaml(tmpdir): example_dir = Path(__file__).parent.parent.parent / "config_examples" simulator = load_simulator_from_yaml(example_dir / "simulator.yaml") - assert isinstance(simulator, VisCPU) + assert isinstance(simulator, MatVis) assert simulator.ref_time == "mean" - sim2 = VisCPU.from_yaml(example_dir / "simulator.yaml") + sim2 = MatVis.from_yaml(example_dir / "simulator.yaml") assert sim2.ref_time == simulator.ref_time assert sim2.diffuse_ability == simulator.diffuse_ability diff --git a/hera_sim/tests/test_vis_cli.py b/hera_sim/tests/test_vis_cli.py index 70983da8..c190e78d 100644 --- a/hera_sim/tests/test_vis_cli.py +++ b/hera_sim/tests/test_vis_cli.py @@ -60,7 +60,7 @@ def test_vis_cli(tmp_path_factory): parser, [ str(cfg), - str(DATA_PATH / "viscpu.yaml"), + str(DATA_PATH / "matvis_cpu.yaml"), "--compress", str(outdir / "compression-cache.npy"), "--normalize_beams", @@ -85,12 +85,12 @@ def test_vis_cli_dry(tmp_path_factory): parser, [ str(cfg), - str(DATA_PATH / "viscpu.yaml"), + str(DATA_PATH / "matvis_cpu.yaml"), "--compress", str(outdir / "compression-cache.npy"), "--dry", "--object_name", - "viscpu", + "matvis", ], ) diff --git a/hera_sim/tests/testdata/hera-sim-vis-config/viscpu.yaml b/hera_sim/tests/testdata/hera-sim-vis-config/matvis_cpu.yaml similarity index 83% rename from hera_sim/tests/testdata/hera-sim-vis-config/viscpu.yaml rename to hera_sim/tests/testdata/hera-sim-vis-config/matvis_cpu.yaml index cc5c929f..a1136bb3 100755 --- a/hera_sim/tests/testdata/hera-sim-vis-config/viscpu.yaml +++ b/hera_sim/tests/testdata/hera-sim-vis-config/matvis_cpu.yaml @@ -1,4 +1,4 @@ -simulator: VisCPU +simulator: MatVis precision: 2 ref_time: mean correct_source_positions: true diff --git a/hera_sim/tests/testdata/hera-sim-vis-config/visgpu.yaml b/hera_sim/tests/testdata/hera-sim-vis-config/matvis_gpu.yaml similarity index 85% rename from hera_sim/tests/testdata/hera-sim-vis-config/visgpu.yaml rename to hera_sim/tests/testdata/hera-sim-vis-config/matvis_gpu.yaml index 60c0f93f..c65f26ff 100755 --- a/hera_sim/tests/testdata/hera-sim-vis-config/visgpu.yaml +++ b/hera_sim/tests/testdata/hera-sim-vis-config/matvis_gpu.yaml @@ -1,4 +1,4 @@ -simulator: VisCPU +simulator: MatVis precision: 2 ref_time: mean correct_source_positions: true diff --git a/hera_sim/visibilities/__init__.py b/hera_sim/visibilities/__init__.py index 9bcfdaa8..c7487ef3 100644 --- a/hera_sim/visibilities/__init__.py +++ b/hera_sim/visibilities/__init__.py @@ -20,7 +20,7 @@ try: - from .vis_cpu import VisCPU + from .matvis import MatVis except (ImportError, NameError): # pragma: no cover pass diff --git a/hera_sim/visibilities/matvis.py b/hera_sim/visibilities/matvis.py new file mode 100644 index 00000000..1f620d64 --- /dev/null +++ b/hera_sim/visibilities/matvis.py @@ -0,0 +1,517 @@ +"""Wrapper for matvis visibility simulator.""" +from __future__ import annotations + +import astropy.units as u +import itertools +import logging +import numpy as np +from astropy.coordinates import EarthLocation +from astropy.time import Time +from matvis import HAVE_GPU, __version__ +from matvis import conversions as convs +from matvis import cpu, gpu +from matvis.cpu import _evaluate_beam_cpu, _wrangle_beams +from pyuvdata import UVData +from pyuvdata import utils as uvutils + +from .simulators import ModelData, VisibilitySimulator + +logger = logging.getLogger(__name__) + + +class MatVis(VisibilitySimulator): + """ + matvis visibility simulator. + + This is a fast, matrix-baed visibility simulator. + + Parameters + ---------- + polarized : bool, optional + Whether to calculate polarized visibilities or not. By default does polarization + iff multiple polarizations exist in the UVData object. The behaviour of the + simulator is that if requesting polarized output and only a subset of the + simulated pols are available in the UVdata object, the code will issue a warning + but otherwise continue happily, throwing away the simulated pols it can't store + in the UVdata object. Conversely, if polarization is not requested and multiple + polarizations are present on the UVData object, it will error unless + ``allow_empty_pols`` is set to True (in which case it will warn but continue). + The "unpolarized" output of ``matvis`` is expected to be XX polarization, which + corresponds to whatever the UVData object considers to be the x-direction + (default East). + precision : int, optional + Which precision level to use for floats and complex numbers. + Allowed values: + - 1: float32, complex64 + - 2: float64, complex128 + use_gpu : bool, optional + Whether to use the GPU version of matvis or not. Default: False. + mpi_comm : MPI communicator + MPI communicator, for parallelization. + ref_time + A reference time for computing adjustments to the co-ordinate transforms using + astropy. For best fidelity, set this to a mid-point of your observation times. + If specified as a string, this must either use the 'isot' format and 'utc' + scale, or be one of "mean", "min" or "max". If any of the latter, the value + ll be calculated from the input data directly. + correct_source_positions + Whether to correct the source positions using astropy and the reference time. + Default is True if `ref_time` is given otherwise False. + check_antenna_conjugation + Whether to check the antenna conjugation. Default is True. This is a fairly + heavy operation if there are many antennas and/or many times, and can be + safely ignored if the data_model was created from a config file. + **kwargs + Passed through to :class:`~.simulators.VisibilitySimulator`. + + """ + + conjugation_convention = "ant1 1: + raise RuntimeError("MPI is not yet supported in GPU mode") + + if use_gpu and not HAVE_GPU: + raise ImportError( + "GPU acceleration requires installing with `pip install hera_sim[gpu]`." + ) + + self._matvis = gpu.simulate if use_gpu else cpu.simulate + + self.use_gpu = use_gpu + self.mpi_comm = mpi_comm + self.ref_time = ref_time + self.correct_source_positions = ( + (ref_time is not None) + if correct_source_positions is None + else correct_source_positions + ) + self.check_antenna_conjugation = check_antenna_conjugation + self._functions_to_profile = (self._matvis, _wrangle_beams, _evaluate_beam_cpu) + self.kwargs = kwargs + + def validate(self, data_model: ModelData): + """Checks for correct input format.""" + # N(N-1)/2 unique cross-correlations + N autocorrelations. + if data_model.uvdata.Nbls != data_model.n_ant * (data_model.n_ant + 1) / 2: + raise ValueError( + "MatVis requires using every pair of antennas, " + "but the UVData object does not comply." + ) + + logger.info("Checking baseline-time axis shape") + if len(data_model.uvdata.data_array) != len( + data_model.uvdata.get_antpairs() + ) * len(data_model.lsts): + raise ValueError("MatVis requires that every baseline uses the same LSTS.") + + if self.check_antenna_conjugation: + logger.info("Checking antenna conjugation") + # TODO: the following is extremely slow. If possible, it would be good to + # find a better way to do it. + if any( + len(data_model.uvdata.antpair2ind(ai, aj)) > 0 + and len(data_model.uvdata.antpair2ind(aj, ai)) > 0 + for ai, aj in data_model.uvdata.get_antpairs() + if ai != aj + ): + raise ValueError( + "MatVis requires that baselines be in a conjugation in which " + "antenna order doesn't change with time!" + ) + + uvbeam = data_model.beams[0] # Representative beam + uvdata = data_model.uvdata + + # Now check that we only have linear polarizations (don't allow pseudo-stokes) + if any(pol not in [-5, -6, -7, -8] for pol in uvdata.polarization_array): + raise ValueError( + """ + While UVData allows non-linear polarizations, they are not suitable + for generating simulations. Please convert your UVData object to use + linear polarizations before simulating (and convert back to + other polarizations afterwards if necessary). + """ + ) + + do_pol = self._check_if_polarized(data_model) + if do_pol: + # Number of feeds must be two if doing polarized + try: + nfeeds = uvbeam.data_array.shape[1 if uvbeam.future_array_shapes else 2] + except AttributeError: + # TODO: the following assumes that analytic beams are 2 feeds unless + # otherwise specified. This should be fixed at the AnalyticBeam API + # level. + nfeeds = getattr(uvbeam, "Nfeeds", 2) + + assert nfeeds == 2 + + def estimate_memory(self, data_model: ModelData) -> float: + """ + Estimates the memory usage of the model. + + Parameters + ---------- + data_model : ModelData + The model data. + + Returns + ------- + float + Estimated memory usage in GB. + """ + bm = data_model.beams[0] + nt = len(data_model.lsts) + nax = getattr(bm, "Naxes_vec", 1) + nfd = getattr(bm, "Nfeeds", 1) + nant = len(data_model.uvdata.antenna_names) + nsrc = len(data_model.sky_model.ra) + nbeam = len(data_model.beams) + nf = len(data_model.freqs) + + try: + nbmpix = bm.data_array[..., 0, :].size + except AttributeError: + nbmpix = 0 + + all_floats = ( + nf * nt * nfd**2 * nant**2 + + nant * nsrc * nax * nfd / 2 # visibilities + + nf * nbeam * nbmpix # per-antenna vis + + nax * nfd * nbeam * nsrc / 2 # raw beam + + 3 * nant # interpolated beam + + nsrc * nf # antenna positions + + nt * 9 # source fluxes + + 3 * nsrc # rotation matrices + + 3 * nsrc + + nant * nsrc / 2 # source positions (topo and eq) # tau. + ) + + return all_floats * self._precision * 4 / 1024**3 + + def correct_point_source_pos( + self, + data_model: ModelData, + obstime: str | Time | None = None, + frame: str = "icrs", + ) -> tuple[np.ndarray, np.ndarray]: + """Apply correction to source RA and Dec positions to improve accuracy. + + This uses an astropy-based coordinate correction, computed for a single + reference time, to shift the source RA and Dec coordinates to ones that + produce more accurate Alt/Az positions at the location of the array. + + Parameters + ---------- + obstime : str or astropy.Time + Specifies the time of the reference observation used to compute the + coordinate correction. If specified as a string, this must either use the + 'isot' format and 'utc' scale, or be one of "mean", "min" or "max". If any + of the latter, the ``data_model`` will be used to generate the reference + time. + + frame : str, optional + Which frame that the original RA and Dec positions are specified + in. Any system recognized by ``astropy.SkyCoord`` can be used. + + Returns + ------- + ra, dec + The updated source positions. + """ + # Check input reference time + if self.ref_time is not None: + obstime = self.ref_time + + if isinstance(obstime, str): + if obstime == "mean": + obstime = Time(data_model.uvdata.time_array.mean(), format="jd") + elif obstime == "min": + obstime = Time(data_model.uvdata.time_array.min(), format="jd") + elif obstime == "max": + obstime = Time(data_model.uvdata.time_array.max(), format="jd") + else: + obstime = Time(obstime, format="isot", scale="utc") + elif not isinstance(obstime, Time): + raise TypeError("`obstime` must be a string or astropy.Time object") + + # Get reference location + # location = EarthLocation.from_geodetic(lat=lat, lon=lon, height=alt) + location = EarthLocation.from_geocentric( + *data_model.uvdata.telescope_location, unit=u.m + ) + + # Apply correction to point source positions + logger.info("Correcting Source Positions...") + ra, dec = data_model.sky_model.ra, data_model.sky_model.dec + return convs.equatorial_to_eci_coords( + ra, dec, obstime, location, unit="rad", frame=frame + ) + + def _check_if_polarized(self, data_model: ModelData) -> bool: + p = data_model.uvdata.polarization_array + # We only do a non-polarized simulation if UVData has only XX or YY polarization + return len(p) != 1 or uvutils.polnum2str(p[0]) not in ["xx", "yy"] + + def get_eq2tops(self, uvdata: UVData, lsts: np.ndarray): + """ + Calculate transformations from equatorial to topocentric coords. + + Returns + ------- + array_like of self._real_dtype + The set of 3x3 transformation matrices converting equatorial + to topocenteric co-ordinates at each LST. + Shape=(NTIMES, 3, 3). + """ + latitude = uvdata.telescope_location_lat_lon_alt[0] # rad + return np.array( + [convs.eci_to_enu_matrix(lst, latitude) for lst in lsts], + dtype=self._real_dtype, + ) + + def get_feed(self, uvdata) -> str: + """Get the feed to use from the beam, given the UVData object. + + Only applies for an *unpolarized* simulation (for a polarized sim, all feeds + are used). + """ + return uvutils.polnum2str(uvdata.polarization_array[0])[0] + + def simulate(self, data_model): + """ + Calls :func:matvis to perform the visibility calculation. + + Returns + ------- + array_like of self._complex_dtype + Visibilities. Shape=self.uvdata.data_array.shape. + """ + polarized = self._check_if_polarized(data_model) + feed = self.get_feed(data_model.uvdata) + + # Setup MPI info if enabled + if self.mpi_comm is not None: + myid = self.mpi_comm.Get_rank() + nproc = self.mpi_comm.Get_size() + + if self.correct_source_positions: + ra, dec = self.correct_point_source_pos(data_model) + logger.info("Done correcting source positions.") + else: + ra, dec = data_model.sky_model.ra, data_model.sky_model.dec + + logger.info("Getting Equatorial Coordinates") + crd_eq = convs.point_source_crd_eq(ra, dec) + + # Convert equatorial to topocentric coords + logger.info("Getting Rotation Matrices") + eq2tops = self.get_eq2tops(data_model.uvdata, data_model.lsts) + + # The following are antenna positions in the order that they are + # in the uvdata.data_array + active_antpos, ant_list = data_model.uvdata.get_ENU_antpos(pick_data_ants=True) + + # Get pixelized beams if required + logger.info("Preparing Beams...") + beam_list = [ + convs.prepare_beam( + beam, + polarized=polarized, + use_feed=feed, + ) + for beam in data_model.beams + ] + beam_ids = np.array( + [ + data_model.beam_ids[nm] + for i, nm in zip( + data_model.uvdata.antenna_numbers, data_model.uvdata.antenna_names + ) + if i in ant_list + ] + ) + + # Get all the polarizations required to be simulated. + req_pols = self._get_req_pols( + data_model.uvdata, data_model.beams[0], polarized=polarized + ) + + # Empty visibility array + if np.all(data_model.uvdata.data_array == 0): + # Here, we don't make new memory, because that is just a whole extra copy + # of the largest array in the calculation. Instead we fill the data_array + # directly. + visfull = data_model.uvdata.data_array + else: + visfull = np.zeros_like( + data_model.uvdata.data_array, dtype=self._complex_dtype + ) + + for i, freq in enumerate(data_model.freqs): + # Divide tasks between MPI workers if needed + if self.mpi_comm is not None and i % nproc != myid: + continue + + logger.info(f"Simulating Frequency {i+1}/{len(data_model.freqs)}") + + # Call matvis function to simulate visibilities + vis = self._matvis( + antpos=active_antpos, + freq=freq, + eq2tops=eq2tops, + crd_eq=crd_eq, + I_sky=data_model.sky_model.stokes[0, i].to("Jy").value, + beam_list=beam_list, + beam_idx=beam_ids, + beam_spline_opts=data_model.beams.spline_interp_opts, + precision=self._precision, + polarized=polarized, + **self.kwargs, + ) + + logger.info("... re-ordering visibilities...") + self._reorder_vis( + req_pols, data_model.uvdata, visfull[:, 0, i], vis, ant_list, polarized + ) + + # Reduce visfull array if in MPI mode + if self.mpi_comm is not None: + visfull = self._reduce_mpi(visfull, myid) + + if visfull is data_model.uvdata.data_array: + # In the case that we were just fulling up the data array the whole time, + # we return zero, because this will be added to the data_array in the + # wrapper simulate() function. + return 0 + else: + return visfull + + def _reorder_vis(self, req_pols, uvdata, visfull, vis, ant_list, polarized): + ant1idx, ant2idx = np.triu_indices(vis.shape[-1]) + + try: + if ( + getattr(uvdata, "blt_order", None) == ("time", "ant1") + and sorted(req_pols) == req_pols + ): + logger.info("Using direct setting of data without reordering") + # This is the best case scenario -- no need to reorder anything. + # It is also MUCH MUCH faster! + start_shape = visfull.shape + visfull.shape = (np.prod(visfull.shape),) # flatten without copying + n = (uvdata.Nblts // vis.shape[0]) * len(req_pols) + for i, vis_here in enumerate(vis): + if polarized: + vis_here = vis_here.transpose(2, 3, 0, 1)[ + ant1idx, ant2idx + ].reshape((-1,)) + else: + vis_here = vis_here[:, ant1idx, ant2idx].reshape((-1,)) + + visfull[(i * n) : ((i + 1) * n)] = vis_here + visfull.shape = start_shape + return + except AttributeError: + pass + + logger.info( + f"Reordering baselines. Pols sorted: {sorted(req_pols) == req_pols}. " + f"Pols = {req_pols}. blt_order = {uvdata.blt_order}" + ) + for ant1, ant2 in zip(ant1idx, ant2idx): # go through indices in output + # get official "antenna numbers" corresponding to these indices + antnum1, antnum2 = ant_list[ant1], ant_list[ant2] + + # get all blt indices corresponding to this antpair + indx = uvdata.antpair2ind(antnum1, antnum2) + if len(indx) == 0: + # maybe we chose the wrong ordering according to the data. Then + # we just conjugate. + indx = uvdata.antpair2ind(antnum2, antnum1) + vis_here = vis[..., ant2, ant1] + else: + vis_here = vis[..., ant1, ant2] + + if polarized: + for p, (p1, p2) in enumerate(req_pols): + visfull[indx, p] = vis_here[:, p1, p2] + else: + visfull[indx, 0] = vis_here + + def _get_req_pols(self, uvdata, uvbeam, polarized: bool) -> list[tuple[int, int]]: + if not polarized: + return [(0, 0)] + + # TODO: this can be updated to just access uvbeam.feed_array once the + # AnalyticBeam API has been improved. + feeds = list(getattr(uvbeam, "feed_array", ["x", "y"])) + + # In order to get all 4 visibility polarizations for a dual feed system + vispols = set() + for p1, p2 in itertools.combinations_with_replacement(feeds, 2): + vispols.add(p1 + p2) + vispols.add(p2 + p1) + avail_pols = { + vispol: (feeds.index(vispol[0]), feeds.index(vispol[1])) + for vispol in vispols + } + # Get the mapping from uvdata pols to uvbeam pols + uvdata_pols = [ + uvutils.polnum2str(polnum, getattr(uvbeam, "x_orientation", None)) + for polnum in uvdata.polarization_array + ] + if any(pol not in avail_pols for pol in uvdata_pols): + raise ValueError( + "Not all polarizations in UVData object are in your beam. " + f"UVData polarizations = {uvdata_pols}. " + f"UVBeam polarizations = {list(avail_pols.keys())}" + ) + + return [avail_pols[pol] for pol in uvdata_pols] + + def _reduce_mpi(self, visfull, myid): # pragma: no cover + from mpi4py.MPI import SUM + + _visfull = np.zeros(visfull.shape, dtype=visfull.dtype) + self.mpi_comm.Reduce(visfull, _visfull, op=SUM, root=0) + if myid == 0: + return _visfull + else: + return 0 # workers return 0 + + def compress_data_model(self, data_model: ModelData): + data_model.uvdata.uvw_array = 0 + # data_model.uvdata.baseline_array = 0 + data_model.uvdata.integration_time = data_model.uvdata.integration_time.item(0) + + def restore_data_model(self, data_model: ModelData): + uv_obj = data_model.uvdata + uv_obj.integration_time = np.repeat( + uv_obj.integration_time, uv_obj.Nbls * uv_obj.Ntimes + ) + uv_obj.set_uvws_from_antenna_positions() diff --git a/hera_sim/visibilities/vis_cpu.py b/hera_sim/visibilities/vis_cpu.py index 8aa9a7ce..e69de29b 100644 --- a/hera_sim/visibilities/vis_cpu.py +++ b/hera_sim/visibilities/vis_cpu.py @@ -1,519 +0,0 @@ -"""Wrapper for vis_cpu visibility simulator.""" -from __future__ import annotations - -import astropy.units as u -import itertools -import logging -import numpy as np -from astropy.coordinates import EarthLocation -from astropy.time import Time -from pyuvdata import UVData -from pyuvdata import utils as uvutils - -from vis_cpu import HAVE_GPU, __version__ -from vis_cpu import conversions as convs -from vis_cpu import vis_cpu, vis_gpu -from vis_cpu.cpu import _evaluate_beam_cpu, _wrangle_beams - -from .simulators import ModelData, VisibilitySimulator - -logger = logging.getLogger(__name__) - - -class VisCPU(VisibilitySimulator): - """ - vis_cpu visibility simulator. - - This is a fast, simple visibility simulator that is intended to be - replaced by vis_gpu. - - Parameters - ---------- - polarized : bool, optional - Whether to calculate polarized visibilities or not. By default does polarization - iff multiple polarizations exist in the UVData object. The behaviour of the - simulator is that if requesting polarized output and only a subset of the - simulated pols are available in the UVdata object, the code will issue a warning - but otherwise continue happily, throwing away the simulated pols it can't store - in the UVdata object. Conversely, if polarization is not requested and multiple - polarizations are present on the UVData object, it will error unless - ``allow_empty_pols`` is set to True (in which case it will warn but continue). - The "unpolarized" output of ``vis_cpu`` is expected to be XX polarization, which - corresponds to whatever the UVData object considers to be the x-direction - (default East). - precision : int, optional - Which precision level to use for floats and complex numbers. - Allowed values: - - 1: float32, complex64 - - 2: float64, complex128 - use_gpu : bool, optional - Whether to use the GPU version of vis_cpu or not. Default: False. - mpi_comm : MPI communicator - MPI communicator, for parallelization. - ref_time - A reference time for computing adjustments to the co-ordinate transforms using - astropy. For best fidelity, set this to a mid-point of your observation times. - If specified as a string, this must either use the 'isot' format and 'utc' - scale, or be one of "mean", "min" or "max". If any of the latter, the value - ll be calculated from the input data directly. - correct_source_positions - Whether to correct the source positions using astropy and the reference time. - Default is True if `ref_time` is given otherwise False. - check_antenna_conjugation - Whether to check the antenna conjugation. Default is True. This is a fairly - heavy operation if there are many antennas and/or many times, and can be - safely ignored if the data_model was created from a config file. - **kwargs - Passed through to :class:`~.simulators.VisibilitySimulator`. - - """ - - conjugation_convention = "ant1 1: - raise RuntimeError("MPI is not yet supported in GPU mode") - - if use_gpu and not HAVE_GPU: - raise ImportError( - "GPU acceleration requires hera_gpu (`pip install hera_sim[gpu]`)." - ) - - self._vis_cpu = vis_gpu if use_gpu else vis_cpu - - self.use_gpu = use_gpu - self.mpi_comm = mpi_comm - self.ref_time = ref_time - self.correct_source_positions = ( - (ref_time is not None) - if correct_source_positions is None - else correct_source_positions - ) - self.check_antenna_conjugation = check_antenna_conjugation - self._functions_to_profile = (self._vis_cpu, _wrangle_beams, _evaluate_beam_cpu) - self.kwargs = kwargs - - def validate(self, data_model: ModelData): - """Checks for correct input format.""" - # N(N-1)/2 unique cross-correlations + N autocorrelations. - if data_model.uvdata.Nbls != data_model.n_ant * (data_model.n_ant + 1) / 2: - raise ValueError( - "VisCPU requires using every pair of antennas, " - "but the UVData object does not comply." - ) - - logger.info("Checking baseline-time axis shape") - if len(data_model.uvdata.data_array) != len( - data_model.uvdata.get_antpairs() - ) * len(data_model.lsts): - raise ValueError("VisCPU requires that every baseline uses the same LSTS.") - - if self.check_antenna_conjugation: - logger.info("Checking antenna conjugation") - # TODO: the following is extremely slow. If possible, it would be good to - # find a better way to do it. - if any( - len(data_model.uvdata.antpair2ind(ai, aj)) > 0 - and len(data_model.uvdata.antpair2ind(aj, ai)) > 0 - for ai, aj in data_model.uvdata.get_antpairs() - if ai != aj - ): - raise ValueError( - "VisCPU requires that baselines be in a conjugation in which " - "antenna order doesn't change with time!" - ) - - uvbeam = data_model.beams[0] # Representative beam - uvdata = data_model.uvdata - - # Now check that we only have linear polarizations (don't allow pseudo-stokes) - if any(pol not in [-5, -6, -7, -8] for pol in uvdata.polarization_array): - raise ValueError( - """ - While UVData allows non-linear polarizations, they are not suitable - for generating simulations. Please convert your UVData object to use - linear polarizations before simulating (and convert back to - other polarizations afterwards if necessary). - """ - ) - - do_pol = self._check_if_polarized(data_model) - if do_pol: - # Number of feeds must be two if doing polarized - try: - nfeeds = uvbeam.data_array.shape[1 if uvbeam.future_array_shapes else 2] - except AttributeError: - # TODO: the following assumes that analytic beams are 2 feeds unless - # otherwise specified. This should be fixed at the AnalyticBeam API - # level. - nfeeds = getattr(uvbeam, "Nfeeds", 2) - - assert nfeeds == 2 - - def estimate_memory(self, data_model: ModelData) -> float: - """ - Estimates the memory usage of the model. - - Parameters - ---------- - data_model : ModelData - The model data. - - Returns - ------- - float - Estimated memory usage in GB. - """ - bm = data_model.beams[0] - nt = len(data_model.lsts) - nax = getattr(bm, "Naxes_vec", 1) - nfd = getattr(bm, "Nfeeds", 1) - nant = len(data_model.uvdata.antenna_names) - nsrc = len(data_model.sky_model.ra) - nbeam = len(data_model.beams) - nf = len(data_model.freqs) - - try: - nbmpix = bm.data_array[..., 0, :].size - except AttributeError: - nbmpix = 0 - - all_floats = ( - nf * nt * nfd**2 * nant**2 - + nant * nsrc * nax * nfd / 2 # visibilities - + nf * nbeam * nbmpix # per-antenna vis - + nax * nfd * nbeam * nsrc / 2 # raw beam - + 3 * nant # interpolated beam - + nsrc * nf # antenna positions - + nt * 9 # source fluxes - + 3 * nsrc # rotation matrices - + 3 * nsrc - + nant * nsrc / 2 # source positions (topo and eq) # tau. - ) - - return all_floats * self._precision * 4 / 1024**3 - - def correct_point_source_pos( - self, - data_model: ModelData, - obstime: str | Time | None = None, - frame: str = "icrs", - ) -> tuple[np.ndarray, np.ndarray]: - """Apply correction to source RA and Dec positions to improve accuracy. - - This uses an astropy-based coordinate correction, computed for a single - reference time, to shift the source RA and Dec coordinates to ones that - produce more accurate Alt/Az positions at the location of the array. - - Parameters - ---------- - obstime : str or astropy.Time - Specifies the time of the reference observation used to compute the - coordinate correction. If specified as a string, this must either use the - 'isot' format and 'utc' scale, or be one of "mean", "min" or "max". If any - of the latter, the ``data_model`` will be used to generate the reference - time. - - frame : str, optional - Which frame that the original RA and Dec positions are specified - in. Any system recognized by ``astropy.SkyCoord`` can be used. - - Returns - ------- - ra, dec - The updated source positions. - """ - # Check input reference time - if self.ref_time is not None: - obstime = self.ref_time - - if isinstance(obstime, str): - if obstime == "mean": - obstime = Time(data_model.uvdata.time_array.mean(), format="jd") - elif obstime == "min": - obstime = Time(data_model.uvdata.time_array.min(), format="jd") - elif obstime == "max": - obstime = Time(data_model.uvdata.time_array.max(), format="jd") - else: - obstime = Time(obstime, format="isot", scale="utc") - elif not isinstance(obstime, Time): - raise TypeError("`obstime` must be a string or astropy.Time object") - - # Get reference location - # location = EarthLocation.from_geodetic(lat=lat, lon=lon, height=alt) - location = EarthLocation.from_geocentric( - *data_model.uvdata.telescope_location, unit=u.m - ) - - # Apply correction to point source positions - logger.info("Correcting Source Positions...") - ra, dec = data_model.sky_model.ra, data_model.sky_model.dec - return convs.equatorial_to_eci_coords( - ra, dec, obstime, location, unit="rad", frame=frame - ) - - def _check_if_polarized(self, data_model: ModelData) -> bool: - p = data_model.uvdata.polarization_array - # We only do a non-polarized simulation if UVData has only XX or YY polarization - return len(p) != 1 or uvutils.polnum2str(p[0]) not in ["xx", "yy"] - - def get_eq2tops(self, uvdata: UVData, lsts: np.ndarray): - """ - Calculate transformations from equatorial to topocentric coords. - - Returns - ------- - array_like of self._real_dtype - The set of 3x3 transformation matrices converting equatorial - to topocenteric co-ordinates at each LST. - Shape=(NTIMES, 3, 3). - """ - latitude = uvdata.telescope_location_lat_lon_alt[0] # rad - return np.array( - [convs.eci_to_enu_matrix(lst, latitude) for lst in lsts], - dtype=self._real_dtype, - ) - - def get_feed(self, uvdata) -> str: - """Get the feed to use from the beam, given the UVData object. - - Only applies for an *unpolarized* simulation (for a polarized sim, all feeds - are used). - """ - return uvutils.polnum2str(uvdata.polarization_array[0])[0] - - def simulate(self, data_model): - """ - Calls :func:vis_cpu to perform the visibility calculation. - - Returns - ------- - array_like of self._complex_dtype - Visibilities. Shape=self.uvdata.data_array.shape. - """ - polarized = self._check_if_polarized(data_model) - feed = self.get_feed(data_model.uvdata) - - # Setup MPI info if enabled - if self.mpi_comm is not None: - myid = self.mpi_comm.Get_rank() - nproc = self.mpi_comm.Get_size() - - if self.correct_source_positions: - ra, dec = self.correct_point_source_pos(data_model) - logger.info("Done correcting source positions.") - else: - ra, dec = data_model.sky_model.ra, data_model.sky_model.dec - - logger.info("Getting Equatorial Coordinates") - crd_eq = convs.point_source_crd_eq(ra, dec) - - # Convert equatorial to topocentric coords - logger.info("Getting Rotation Matrices") - eq2tops = self.get_eq2tops(data_model.uvdata, data_model.lsts) - - # The following are antenna positions in the order that they are - # in the uvdata.data_array - active_antpos, ant_list = data_model.uvdata.get_ENU_antpos(pick_data_ants=True) - - # Get pixelized beams if required - logger.info("Preparing Beams...") - beam_list = [ - convs.prepare_beam( - beam, - polarized=polarized, - use_feed=feed, - ) - for beam in data_model.beams - ] - beam_ids = np.array( - [ - data_model.beam_ids[nm] - for i, nm in zip( - data_model.uvdata.antenna_numbers, data_model.uvdata.antenna_names - ) - if i in ant_list - ] - ) - - # Get all the polarizations required to be simulated. - req_pols = self._get_req_pols( - data_model.uvdata, data_model.beams[0], polarized=polarized - ) - - # Empty visibility array - if np.all(data_model.uvdata.data_array == 0): - # Here, we don't make new memory, because that is just a whole extra copy - # of the largest array in the calculation. Instead we fill the data_array - # directly. - visfull = data_model.uvdata.data_array - else: - visfull = np.zeros_like( - data_model.uvdata.data_array, dtype=self._complex_dtype - ) - - for i, freq in enumerate(data_model.freqs): - # Divide tasks between MPI workers if needed - if self.mpi_comm is not None and i % nproc != myid: - continue - - logger.info(f"Simulating Frequency {i+1}/{len(data_model.freqs)}") - - # Call vis_cpu function to simulate visibilities - vis = self._vis_cpu( - antpos=active_antpos, - freq=freq, - eq2tops=eq2tops, - crd_eq=crd_eq, - I_sky=data_model.sky_model.stokes[0, i].to("Jy").value, - beam_list=beam_list, - beam_idx=beam_ids, - beam_spline_opts=data_model.beams.spline_interp_opts, - precision=self._precision, - polarized=polarized, - **self.kwargs, - ) - - logger.info("... re-ordering visibilities...") - self._reorder_vis( - req_pols, data_model.uvdata, visfull[:, 0, i], vis, ant_list, polarized - ) - - # Reduce visfull array if in MPI mode - if self.mpi_comm is not None: - visfull = self._reduce_mpi(visfull, myid) - - if visfull is data_model.uvdata.data_array: - # In the case that we were just fulling up the data array the whole time, - # we return zero, because this will be added to the data_array in the - # wrapper simulate() function. - return 0 - else: - return visfull - - def _reorder_vis(self, req_pols, uvdata, visfull, vis, ant_list, polarized): - ant1idx, ant2idx = np.triu_indices(vis.shape[-1]) - - try: - if ( - getattr(uvdata, "blt_order", None) == ("time", "ant1") - and sorted(req_pols) == req_pols - ): - logger.info("Using direct setting of data without reordering") - # This is the best case scenario -- no need to reorder anything. - # It is also MUCH MUCH faster! - start_shape = visfull.shape - visfull.shape = (np.prod(visfull.shape),) # flatten without copying - n = (uvdata.Nblts // vis.shape[0]) * len(req_pols) - for i, vis_here in enumerate(vis): - if polarized: - vis_here = vis_here.transpose(2, 3, 0, 1)[ - ant1idx, ant2idx - ].reshape((-1,)) - else: - vis_here = vis_here[:, ant1idx, ant2idx].reshape((-1,)) - - visfull[(i * n) : ((i + 1) * n)] = vis_here - visfull.shape = start_shape - return - except AttributeError: - pass - - logger.info( - f"Reordering baselines. Pols sorted: {sorted(req_pols) == req_pols}. " - f"Pols = {req_pols}. blt_order = {uvdata.blt_order}" - ) - for ant1, ant2 in zip(ant1idx, ant2idx): # go through indices in output - # get official "antenna numbers" corresponding to these indices - antnum1, antnum2 = ant_list[ant1], ant_list[ant2] - - # get all blt indices corresponding to this antpair - indx = uvdata.antpair2ind(antnum1, antnum2) - if len(indx) == 0: - # maybe we chose the wrong ordering according to the data. Then - # we just conjugate. - indx = uvdata.antpair2ind(antnum2, antnum1) - vis_here = vis[..., ant2, ant1] - else: - vis_here = vis[..., ant1, ant2] - - if polarized: - for p, (p1, p2) in enumerate(req_pols): - visfull[indx, p] = vis_here[:, p1, p2] - else: - visfull[indx, 0] = vis_here - - def _get_req_pols(self, uvdata, uvbeam, polarized: bool) -> list[tuple[int, int]]: - if not polarized: - return [(0, 0)] - - # TODO: this can be updated to just access uvbeam.feed_array once the - # AnalyticBeam API has been improved. - feeds = list(getattr(uvbeam, "feed_array", ["x", "y"])) - - # In order to get all 4 visibility polarizations for a dual feed system - vispols = set() - for p1, p2 in itertools.combinations_with_replacement(feeds, 2): - vispols.add(p1 + p2) - vispols.add(p2 + p1) - avail_pols = { - vispol: (feeds.index(vispol[0]), feeds.index(vispol[1])) - for vispol in vispols - } - # Get the mapping from uvdata pols to uvbeam pols - uvdata_pols = [ - uvutils.polnum2str(polnum, getattr(uvbeam, "x_orientation", None)) - for polnum in uvdata.polarization_array - ] - if any(pol not in avail_pols for pol in uvdata_pols): - raise ValueError( - "Not all polarizations in UVData object are in your beam. " - f"UVData polarizations = {uvdata_pols}. " - f"UVBeam polarizations = {list(avail_pols.keys())}" - ) - - return [avail_pols[pol] for pol in uvdata_pols] - - def _reduce_mpi(self, visfull, myid): # pragma: no cover - from mpi4py.MPI import SUM - - _visfull = np.zeros(visfull.shape, dtype=visfull.dtype) - self.mpi_comm.Reduce(visfull, _visfull, op=SUM, root=0) - if myid == 0: - return _visfull - else: - return 0 # workers return 0 - - def compress_data_model(self, data_model: ModelData): - data_model.uvdata.uvw_array = 0 - # data_model.uvdata.baseline_array = 0 - data_model.uvdata.integration_time = data_model.uvdata.integration_time.item(0) - - def restore_data_model(self, data_model: ModelData): - uv_obj = data_model.uvdata - uv_obj.integration_time = np.repeat( - uv_obj.integration_time, uv_obj.Nbls * uv_obj.Ntimes - ) - uv_obj.set_uvws_from_antenna_positions() From 966d184beb977a79434f93f422b7728add82c19e Mon Sep 17 00:00:00 2001 From: Steven Murray Date: Mon, 4 Dec 2023 11:03:29 +0100 Subject: [PATCH 2/5] docs: update docstrings/comments from vis_cpu->matvis --- hera_sim/tests/test_compare_pyuvsim.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hera_sim/tests/test_compare_pyuvsim.py b/hera_sim/tests/test_compare_pyuvsim.py index c3bceb08..7d2838ec 100644 --- a/hera_sim/tests/test_compare_pyuvsim.py +++ b/hera_sim/tests/test_compare_pyuvsim.py @@ -151,7 +151,7 @@ def get_beams(beam_type, polarized): ], ) def test_compare_matvis_with_pyuvsim(uvdata_allpols, nsource, beam_type, polarized): - """Compare vis_cpu and pyuvsim simulated visibilities.""" + """Compare matvis and pyuvsim simulated visibilities.""" sky_model = get_sky_model(uvdata_allpols, nsource) # Beam models @@ -159,7 +159,7 @@ def test_compare_matvis_with_pyuvsim(uvdata_allpols, nsource, beam_type, polariz beam_dict = {str(i): 0 for i in range(nants)} # --------------------------------------------------------------------------- - # (1) Run vis_cpu + # (1) Run matvis # --------------------------------------------------------------------------- # Trim unwanted polarizations uvdata_matvis = copy.deepcopy(uvdata_allpols) From 0254b6686baf03cf58270a1f758cc9e239fc1327 Mon Sep 17 00:00:00 2001 From: Steven Murray Date: Wed, 6 Dec 2023 11:17:20 +0100 Subject: [PATCH 3/5] maint: use matvis in setup.cfg instead of vis-cpu --- setup.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 7624189f..e7813aa2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -60,12 +60,12 @@ docs = furo hera-sim ipython + matvis>=1.2.1 nbsphinx numpydoc>=0.8 pyradiosky>=0.1.2 sphinx>=1.8,<7.2 sphinx-autorun - vis-cpu>=1.1.0 gpu = pycuda scikit-cuda @@ -79,9 +79,9 @@ tests = uvtools vis = line-profiler + matvis>=1.2.1 mpi4py pyradiosky>=0.1.2 - vis-cpu>=1.1.0 [tool:pytest] addopts = From 481dd28aa83c20ada9bc6395182abb31159a5b14 Mon Sep 17 00:00:00 2001 From: Steven Murray Date: Wed, 6 Dec 2023 11:32:46 +0100 Subject: [PATCH 4/5] fix: wrong import --- hera_sim/tests/test_vis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hera_sim/tests/test_vis.py b/hera_sim/tests/test_vis.py index 2dad4522..7cc13e47 100644 --- a/hera_sim/tests/test_vis.py +++ b/hera_sim/tests/test_vis.py @@ -7,6 +7,7 @@ from astropy import units from astropy.coordinates.angles import Latitude, Longitude from astropy.units import rad, sday +from matvis import HAVE_GPU from pathlib import Path from pyradiosky import SkyModel from pyuvsim.analyticbeam import AnalyticBeam @@ -22,7 +23,6 @@ VisibilitySimulation, load_simulator_from_yaml, ) -from vis_cpu import HAVE_GPU SIMULATORS = (MatVis, UVSim) From 9c66df934ff0b2bd493f61e8dfd4e62461ce5b75 Mon Sep 17 00:00:00 2001 From: Steven Murray Date: Mon, 11 Dec 2023 13:03:03 +0100 Subject: [PATCH 5/5] fix: VisCPU -> MatVis --- config_examples/simulator.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_examples/simulator.yaml b/config_examples/simulator.yaml index d4473541..9935b89e 100644 --- a/config_examples/simulator.yaml +++ b/config_examples/simulator.yaml @@ -1,4 +1,4 @@ -simulator: VisCPU +simulator: MatVis precision: 2 ref_time: mean correct_source_positions: true