From 30375a5912a750b22b643abb1def4d0429154efe Mon Sep 17 00:00:00 2001 From: "Christopher H. Jordan" Date: Sun, 21 Jan 2024 08:39:02 +0800 Subject: [PATCH] Add comparisons with other MWA FEE beam codes. It seems markdown can't include other markdown files or their elements. This means some info is duplicated across README.md files. Sigh. --- .gitignore | 1 + README.md | 64 +- .../1090008640_2s_40kHz.trunc.ms.tar.gz | Bin 0 -> 72897 bytes comparisons/README.md | 281 ++++ comparisons/everybeam/Makefile | 8 + comparisons/everybeam/everybeam_example.cpp | 91 ++ comparisons/everybeam_example.py | 12 + comparisons/hyperbeam/.cargo/config.toml | 2 + comparisons/hyperbeam/Cargo.lock | 1159 +++++++++++++++++ comparisons/hyperbeam/Cargo.toml | 18 + .../hyperbeam/src/bin/hyperbeam-cuda.rs | 84 ++ comparisons/hyperbeam/src/bin/hyperbeam.rs | 102 ++ comparisons/hyperbeam_example.py | 117 ++ comparisons/mwa_pb_example.py | 83 ++ comparisons/pyuvdata_example.py | 31 + comparisons/run.sh | 189 +++ 16 files changed, 2211 insertions(+), 31 deletions(-) create mode 100644 comparisons/1090008640_2s_40kHz.trunc.ms.tar.gz create mode 100644 comparisons/README.md create mode 100644 comparisons/everybeam/Makefile create mode 100644 comparisons/everybeam/everybeam_example.cpp create mode 100755 comparisons/everybeam_example.py create mode 100644 comparisons/hyperbeam/.cargo/config.toml create mode 100644 comparisons/hyperbeam/Cargo.lock create mode 100644 comparisons/hyperbeam/Cargo.toml create mode 100644 comparisons/hyperbeam/src/bin/hyperbeam-cuda.rs create mode 100644 comparisons/hyperbeam/src/bin/hyperbeam.rs create mode 100755 comparisons/hyperbeam_example.py create mode 100755 comparisons/mwa_pb_example.py create mode 100755 comparisons/pyuvdata_example.py create mode 100755 comparisons/run.sh diff --git a/.gitignore b/.gitignore index e651855..a387970 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ target mwa_full_embedded_element_pattern.h5 /include/mwa_hyperbeam.h +/comparisons/ /examples/fee /examples/fee_parallel /examples/fee_parallel_omp diff --git a/README.md b/README.md index d777582..1b0b912 100644 --- a/README.md +++ b/README.md @@ -222,37 +222,39 @@ maturin develop --release -b pyo3 --features=python,hdf5-static --strip ## Comparing with other FEE beam codes -Below is a table comparing other implementations of the FEE beam code. All -benchmarks were done with unique azimuth and zenith angle directions, and all -on the same system. The CPU is a Ryzen 9 3900X, which has 12 cores and SMT (24 -threads). The CUDA benchmarks uses an NVIDIA GeForce RTX 2070. All benchmarks -were done in serial, unless indicated by "parallel". Python times were taken -by running `time.time()` before and after the calculations. Memory usage is -measured by running `time -v` on the command (not the `time` associated with -your shell; this is usually at `/usr/bin/time`). - -| Code | Number of directions | Duration | Max. memory usage | -|:-----------------|---------------------:|---------:|------------------:| -| [mwa_pb](https://github.com/MWATelescope/mwa_pb) | 500 | 98.8 ms | 134.6 MiB | -| | 100000 | 13.4 s | 5.29 GiB | -| | 1000000 | 139.8 s | 51.6 GiB | -| mwa-reduce (C++) | 500 | 115.2 ms | 48.9 MiB | -| | 10000 | 2.417 s | 6.02 GiB | -| mwa_hyperbeam | 500 | 10.0 ms | 9.75 MiB | -| | 100000 | 1.82 s | 11.3 MiB | -| | 1000000 | 18.1 s | 25.0 MiB | -| mwa_hyperbeam (parallel) | 1000000 | 1.55 s | 88.8 MiB | -| mwa_hyperbeam (via python) | 500 | 20.5 ms | 44.2 MiB | -| | 100000 | 3.70 s | 45.4 MiB | -| | 1000000 | 37.2 s | 59.0 MiB | -| mwa_hyperbeam (via python, parallel) | 1000000 | 2.49 s | 246.6 MiB | -| mwa_hyperbeam (CUDA, single precision) | 1000000 | 450 ms | 253.8 MiB | -| | 1e8 | 3.08 s | 14.26 GiB | - -Not sure what's up with the C++ code. Maybe I'm calling `CalcJonesArray` wrong, -but it uses a huge amount of memory. In any case, `hyperbeam` seems to be -roughly 10x faster. If you know how to compare with `Everybeam`, please let me -know. +A high-level summary is below. Further details and info on how these results +were obtained can be found [here](./comparisons). + +| Package | Config | Number of directions | Duration | Max. memory usage | +|:--------|:-------|---------------------:|---------:|------------------:| +| [mwa_pb](https://github.com/MWATelescope/mwa_pb) | serial | 1 | 14.85 ms | 153 MiB | +| | serial | 1,000 | 130.1 ms | 201 MiB | +| | serial | 300,000 | 37.94 s | 14.4 GiB | +| [pyuvdata](https://github.com/RadioAstronomySoftwareGroup/pyuvdata) | serial | 32,760 | 7.446 s | 653 MiB | +| | serial | 130,320 | 11.85 s | 1.92 GiB | +| [EveryBeam](https://git.astron.nl/RD/EveryBeam) | serial | 1 | 114 µs | 61.7 MiB | +| | serial | 1,000 | 103.9 ms | 61.9 MiB | +| | serial | 300,000 | 31.16 s | 71.1 MiB | +| mwa_hyperbeam | serial | 1 | 32.54 µs | 11.1 MiB | +| | serial | 1,000 | 29.02 ms | 13.3 MiB | +| | parallel | 1,000 | 4.598 ms | 13.5 MiB | +| | serial | 300,000 | 8.610 s | 33.9 MiB | +| | parallel | 300,000 | 596.1 ms | 34.6 MiB | +| | CUDA | 300,000 | 63.70 ms | 134 MiB | +| | CUDA | 999,999 | 164.4 ms | 195 MiB | + +| | mwa_pb | pyuvdata | EveryBeam | mwa_hyperbeam | +|-------------------------------------------|:------------------:|:------------------:|:---------:|:------------------:| +| Can be run in parallel? | :x: | :x: | :x: | :white_check_mark: | +| Parallactic-angle correction? | :x: | :x: | :x: | :white_check_mark: | +| GPU (CUDA/HIP) support? | :x: | :x: | :x: | :white_check_mark: | +| Supports MWA analytic beam? | :white_check_mark: | :x: | :x: | :white_check_mark: | +| Supports per-dipole gains? | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | +| Python interface? | :white_check_mark: | :white_check_mark: | :x:* | :white_check_mark: | +| Can be called from other languages via C? | :x: | :x: | :x: | :white_check_mark: | +| Supports MWA CRAM tile? | :x: | :x: | :x: | :white_check_mark: | + +*: `EveryBeam` has a Python interface, but it does not support the MWA beam. ## Troubleshooting diff --git a/comparisons/1090008640_2s_40kHz.trunc.ms.tar.gz b/comparisons/1090008640_2s_40kHz.trunc.ms.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..973ccec985ef94e1054640c9d5857e5bd1126a2f GIT binary patch literal 72897 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +const size_t NUM_POINTS_TO_SIMULATE = 300'000; +const double RA_RAD = 0.0 * M_PI / 180.0; +const double DEC_RAD = -27.0 * M_PI / 180.0; +const double FREQ_HZ = 150e6; + +int main(int argc, char *argv[]) { + if (argc == 1) { + printf("Expected an argument (path to a measurement set for EveryBeam to use)\n"); + return 1; + } + const char *ms_path = argv[1]; + + casacore::MeasurementSet ms(ms_path); + casacore::MEpoch::ScalarColumn time_column(ms, ms.columnName(casacore::MSMainEnums::TIME)); + casacore::MEpoch first_time = time_column(0); + casacore::Quantity first_utc_time = first_time.get(casacore::Unit("s")); + + everybeam::Options options; + // options.coeff_path = "/usr/local/mwa_full_embedded_element_pattern.h5"; + options.coeff_path = std::getenv("MWA_BEAM_FILE"); + options.beam_normalisation_mode = everybeam::BeamNormalisationMode::kFull; + options.beam_mode = everybeam::BeamMode::kFull; + options.frequency_interpolation = false; + everybeam::telescope::MWA beam = everybeam::telescope::MWA(ms, options); + + std::complex *jones = + (std::complex *)malloc(NUM_POINTS_TO_SIMULATE * 4 * sizeof(std::complex)); + + std::unique_ptr pr = beam.GetPointResponse(first_utc_time.getBaseValue()); + + auto start = std::chrono::high_resolution_clock::now(); + pr->Response(everybeam::BeamMode::kFull, jones, RA_RAD, DEC_RAD, FREQ_HZ, 0, 0); + auto stop = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(stop - start); + printf("time taken to produce 1 simulation (cold cache): %fs\n", (double)duration.count() / 1e6); + + start = std::chrono::high_resolution_clock::now(); + pr->Response(everybeam::BeamMode::kFull, jones, RA_RAD, DEC_RAD, FREQ_HZ, 0, 0); + stop = std::chrono::high_resolution_clock::now(); + duration = std::chrono::duration_cast(stop - start); + printf("time taken to produce 1 simulation (hot cache): %fs\n", (double)duration.count() / 1e6); + + start = std::chrono::high_resolution_clock::now(); + // Attempting to use OpenMP will either cause segfaults or expose the lack + // of thread safety in HDF5 + // #pragma omp parallel for + for (size_t i_jones = 0; i_jones < 1000; ++i_jones) { + std::complex *e = jones + 4 * i_jones; + pr->Response(everybeam::BeamMode::kFull, e, RA_RAD, DEC_RAD, FREQ_HZ, 0, 0); + } + stop = std::chrono::high_resolution_clock::now(); + duration = std::chrono::duration_cast(stop - start); + printf("time taken to produce %ld simulations: %fs\n", 1000, (double)duration.count() / 1e6); + + start = std::chrono::high_resolution_clock::now(); + // Attempting to use OpenMP will either cause segfaults or expose the lack + // of thread safety in HDF5 + // #pragma omp parallel for + for (size_t i_jones = 0; i_jones < NUM_POINTS_TO_SIMULATE; ++i_jones) { + std::complex *e = jones + 4 * i_jones; + pr->Response(everybeam::BeamMode::kFull, e, RA_RAD, DEC_RAD, FREQ_HZ, 0, 0); + } + stop = std::chrono::high_resolution_clock::now(); + duration = std::chrono::duration_cast(stop - start); + printf("time taken to produce %ld simulations: %fs\n", NUM_POINTS_TO_SIMULATE, (double)duration.count() / 1e6); + + printf("\nFirst and last MWA beam responses:\n"); + printf("[%+f%+fi, %+f%+fi\n", jones[0].real(), jones[0].imag(), jones[1].real(), jones[1].imag()); + printf(" %+f%+fi, %+f%+fi]\n", jones[2].real(), jones[2].imag(), jones[3].real(), jones[3].imag()); + printf("[%+f%+fi, %+f%+fi\n", jones[(NUM_POINTS_TO_SIMULATE - 1) * 4 + 0].real(), + jones[(NUM_POINTS_TO_SIMULATE - 1) * 4 + 0].imag(), jones[(NUM_POINTS_TO_SIMULATE - 1) * 4 + 1].real(), + jones[(NUM_POINTS_TO_SIMULATE - 1) * 4 + 1].imag()); + printf(" %+f%+fi, %+f%+fi]\n", jones[(NUM_POINTS_TO_SIMULATE - 1) * 4 + 2].real(), + jones[(NUM_POINTS_TO_SIMULATE - 1) * 4 + 2].imag(), jones[(NUM_POINTS_TO_SIMULATE - 1) * 4 + 3].real(), + jones[(NUM_POINTS_TO_SIMULATE - 1) * 4 + 3].imag()); + + return 0; +} diff --git a/comparisons/everybeam_example.py b/comparisons/everybeam_example.py new file mode 100755 index 0000000..2bbee74 --- /dev/null +++ b/comparisons/everybeam_example.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 + +import os +import sys + +import everybeam as eb + + +ms_path = sys.argv[1] +# This thorws an error on v0.5.3 of EveryBeam (the latest at the time of +# writing), because the MWA isn't supported. +telescope = eb.load_telescope(ms_path, os.environ.get("MWA_BEAM_FILE")) diff --git a/comparisons/hyperbeam/.cargo/config.toml b/comparisons/hyperbeam/.cargo/config.toml new file mode 100644 index 0000000..c729c54 --- /dev/null +++ b/comparisons/hyperbeam/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = "-C target-cpu=native" diff --git a/comparisons/hyperbeam/Cargo.lock b/comparisons/hyperbeam/Cargo.lock new file mode 100644 index 0000000..7a247ee --- /dev/null +++ b/comparisons/hyperbeam/Cargo.lock @@ -0,0 +1,1159 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "built" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9c056b9ed43aee5e064b683aa1ec783e19c6acec7559e3ae931b7490472fbe" +dependencies = [ + "cargo-lock", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "cargo-lock" +version = "8.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031718ddb8f78aa5def78a09e90defe30151d1f6c672f937af4dd916429ed996" +dependencies = [ + "semver", + "serde", + "toml", + "url", +] + +[[package]] +name = "cbindgen" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faeaa693e5a727975a79211b8f35c0cb09b031fdb6eaa4a788bc6713d01488ca" +dependencies = [ + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 1.0.109", + "tempfile", + "toml", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "cuda-config" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee74643f7430213a1a78320f88649de309b20b80818325575e393f848f79f5d" +dependencies = [ + "glob", +] + +[[package]] +name = "cuda-runtime-sys" +version = "0.3.0-alpha.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d070b301187fee3c611e75a425cf12247b7c75c09729dbdef95cb9cb64e8c39" +dependencies = [ + "cuda-config", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "erfa" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63880def87bd7d612b89f9046f5db0961949417f88d831d24596eae555915e5" +dependencies = [ + "thiserror", +] + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "windows-sys", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hdf5" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdcd9b131fd67bb827b386d0dc63d3e74196a14616ef800acf87ca5fef741a10" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "hdf5-derive", + "hdf5-sys", + "hdf5-types", + "lazy_static", + "libc", + "ndarray", + "parking_lot 0.11.2", + "paste", +] + +[[package]] +name = "hdf5-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a77ac6a41e6880594d506118c0b8bc665ec959fe4636e0c84809756d224820" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "hdf5-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4842d5980dc311a7c8933c7b45534fdae84df5ae7939a0ae8e449a56d4beb3d2" +dependencies = [ + "libc", + "libloading", + "pkg-config", + "regex", + "serde", + "serde_derive", + "winreg", +] + +[[package]] +name = "hdf5-types" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b47268c0dfb499b1ffe5638b6e7694e7a87fe49fb92eca998a4346e5483e428f" +dependencies = [ + "ascii", + "cfg-if", + "hdf5-sys", + "libc", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hifitime" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c587aef1280b84f15bfd84eefff9ee55d1a2826e67f089ed263a8c3a029c273" +dependencies = [ + "js-sys", + "lexical-core", + "num-traits", + "serde", + "serde_derive", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "hip-runtime-sys" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901a5d54cfff799dd9e6f6e1d53883bb50afdd92edce7680b1ca299d1805f65a" +dependencies = [ + "libc", +] + +[[package]] +name = "hip-sys" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f399629f98b6249efc10039e949baa70e2ff2bf84d3c30e7a0ca49f179a04287" +dependencies = [ + "hip-runtime-sys", +] + +[[package]] +name = "hyperbeam" +version = "0.1.0" +dependencies = [ + "mwa_hyperbeam", + "ndarray", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lexical-core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", +] + +[[package]] +name = "lexical-parse-float" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" +dependencies = [ + "lexical-parse-integer", + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-parse-integer" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" +dependencies = [ + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-util" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "marlu" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33477b1391477af72a79bf06ed326f7625c21d9e4cd49ddc99d5d8c0d6ba3186" +dependencies = [ + "built", + "cfg-if", + "erfa", + "hifitime", + "itertools", + "lazy_static", + "log", + "ndarray", + "num-complex", + "num-traits", + "rayon", + "tar", + "thiserror", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "mwa_hyperbeam" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361ad095f48d8aeba20b52c03ca3462cce4e7c48ccb80a8c12f10e950fb2f9d8" +dependencies = [ + "cbindgen", + "cc", + "cfg-if", + "cuda-runtime-sys", + "hdf5", + "hip-sys", + "marlu", + "ndarray", + "num-complex", + "panic-message", + "parking_lot 0.12.1", + "rayon", + "thiserror", +] + +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", + "rayon", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "panic-message" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384e52fd8fbd4cbe3c317e8216260c21a0f9134de108cea8a4dd4e7e152c472d" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pkg-config" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" + +[[package]] +name = "proc-macro2" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustix" +version = "0.38.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "serde_json" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b187f0231d56fe41bfb12034819dd2bf336422a5866de41bc3fec4b2e3883e8" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tar" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.4.1", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" + +[[package]] +name = "web-sys" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "serde", + "winapi", +] + +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] diff --git a/comparisons/hyperbeam/Cargo.toml b/comparisons/hyperbeam/Cargo.toml new file mode 100644 index 0000000..d5290dd --- /dev/null +++ b/comparisons/hyperbeam/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "hyperbeam" +version = "0.1.0" +edition = "2021" + +[features] +default = [] +cuda = ["mwa_hyperbeam/cuda"] +hip = ["mwa_hyperbeam/hip"] +gpu-single = ["mwa_hyperbeam/gpu-single"] + +[[bin]] +name = "hyperbeam-cuda" +required-features = ["cuda"] + +[dependencies] +mwa_hyperbeam = "0.7.0" +ndarray = "0.15.6" diff --git a/comparisons/hyperbeam/src/bin/hyperbeam-cuda.rs b/comparisons/hyperbeam/src/bin/hyperbeam-cuda.rs new file mode 100644 index 0000000..2e92f89 --- /dev/null +++ b/comparisons/hyperbeam/src/bin/hyperbeam-cuda.rs @@ -0,0 +1,84 @@ +use mwa_hyperbeam::{ + fee::{FEEBeam, FEEBeamGpu}, + AzEl, +}; +use ndarray::Array2; + +const NUM_POINTS_TO_SIMULATE: usize = 300_000; +const NUM_POINTS_TO_SIMULATE_BIG: usize = 999_999; +const FREQ_HZ: f64 = 150e6; +const EVERYBEAM_AZ_RAD: f64 = 1.745998843813605; +const EVERYBEAM_EL_RAD: f64 = 1.548676626223685; +const MS_DELAYS: &[u32; 16] = &[0; 16]; +const DIPOLE_GAINS: &[f64; 16] = &[1.0; 16]; +const _EVERYBEAM_MWA_LATITUDE_RAD: f64 = -0.466018978039551; + +fn main() { + let beam = FEEBeam::new_from_env().unwrap(); + let gpu_beam: FEEBeamGpu; + let delays_array = Array2::from_shape_vec((1, MS_DELAYS.len()), Vec::from(MS_DELAYS)).unwrap(); + let gains_array = + Array2::from_shape_vec((1, DIPOLE_GAINS.len()), Vec::from(DIPOLE_GAINS)).unwrap(); + let azel = AzEl::from_radians(EVERYBEAM_AZ_RAD, EVERYBEAM_EL_RAD); + let azels = vec![azel; NUM_POINTS_TO_SIMULATE]; + + { + // In order to more fairly compare the cold cache time here with others, + // initialise the GPU beam after the timer starts. + let start = std::time::Instant::now(); + gpu_beam = unsafe { + beam.gpu_prepare( + &[FREQ_HZ as u32], + delays_array.view(), + gains_array.view(), + true, + ) + } + .unwrap(); + let _jones = gpu_beam.calc_jones(&azels, None, false).unwrap(); + println!( + "time taken to produce {} simulations (cold cache): {:?}", + azels.len(), + start.elapsed() + ); + } + { + let start = std::time::Instant::now(); + let _jones = gpu_beam.calc_jones(&azels, None, false).unwrap(); + println!( + "time taken to produce {} simulations (hot cache): {:?}", + azels.len(), + start.elapsed() + ); + } + { + let azels_big = vec![azel; NUM_POINTS_TO_SIMULATE_BIG]; + let start = std::time::Instant::now(); + let jones = gpu_beam.calc_jones(&azels_big, None, false).unwrap(); + println!( + "time taken to produce {} simulations (hot cache): {:?}", + azels_big.len(), + start.elapsed() + ); + + println!("First and last MWA beam responses:"); + let first = jones.first().unwrap(); + let last = jones.last().unwrap(); + println!( + "[{:+.6}{:+.6}i, {:+.6}{:+.6}i", + first[0].re, first[0].im, first[1].re, first[1].im + ); + println!( + " {:+.6}{:+.6}i, {:+.6}{:+.6}i]", + first[2].re, first[2].im, first[3].re, first[3].im + ); + println!( + "[{:+.6}{:+.6}i, {:+.6}{:+.6}i", + last[0].re, last[0].im, last[1].re, last[1].im + ); + println!( + " {:+.6}{:+.6}i, {:+.6}{:+.6}i]", + last[2].re, last[2].im, last[3].re, last[3].im + ); + } +} diff --git a/comparisons/hyperbeam/src/bin/hyperbeam.rs b/comparisons/hyperbeam/src/bin/hyperbeam.rs new file mode 100644 index 0000000..18c5654 --- /dev/null +++ b/comparisons/hyperbeam/src/bin/hyperbeam.rs @@ -0,0 +1,102 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + +use mwa_hyperbeam::{fee::FEEBeam, AzEl}; + +const FREQ_HZ: f64 = 150e6; +const EVERYBEAM_AZ_RAD: f64 = 1.745998843813605; +const EVERYBEAM_EL_RAD: f64 = 1.548676626223685; +const MS_DELAYS: &[u32; 16] = &[0; 16]; +const DIPOLE_GAINS: &[f64; 16] = &[1.0; 16]; +const _EVERYBEAM_MWA_LATITUDE_RAD: f64 = -0.466018978039551; + +static CACHE_HOT: AtomicBool = AtomicBool::new(false); + +fn bench(n: usize, beam: &FEEBeam, azel: AzEl, print_first_and_last: bool) { + let azels = vec![azel; n]; + let start = std::time::Instant::now(); + + let jones = if n == 1 { + beam.calc_jones( + azel, + FREQ_HZ as u32, + MS_DELAYS, + DIPOLE_GAINS, + true, + None, + false, + ) + .unwrap(); + None + } else { + let jones = beam + .calc_jones_array( + &azels, + FREQ_HZ as u32, + MS_DELAYS, + DIPOLE_GAINS, + true, + None, + false, + ) + .unwrap(); + Some(jones) + }; + let duration = start.elapsed(); + + let cache_state = if CACHE_HOT.load(Ordering::Relaxed) { + "hot" + } else { + CACHE_HOT.store(true, Ordering::Relaxed); + "cold" + }; + let plural = if n == 1 { "" } else { "s" }; + println!( + "time taken to produce {n} simulation{plural} ({cache_state} cache): {:?}", + duration + ); + + if let Some(jones) = jones { + if print_first_and_last { + println!("First and last MWA beam responses:"); + let first = jones.first().unwrap(); + let last = jones.last().unwrap(); + println!( + "[{:+.6}{:+.6}i, {:+.6}{:+.6}i", + first[0].re, first[0].im, first[1].re, first[1].im + ); + println!( + " {:+.6}{:+.6}i, {:+.6}{:+.6}i]", + first[2].re, first[2].im, first[3].re, first[3].im + ); + println!( + "[{:+.6}{:+.6}i, {:+.6}{:+.6}i", + last[0].re, last[0].im, last[1].re, last[1].im + ); + println!( + " {:+.6}{:+.6}i, {:+.6}{:+.6}i]", + last[2].re, last[2].im, last[3].re, last[3].im + ); + } + } +} + +fn main() { + let beam = FEEBeam::new_from_env().unwrap(); + let azel = AzEl::from_radians(EVERYBEAM_AZ_RAD, EVERYBEAM_EL_RAD); + + // Check for a CLI argument. If it's there, we'll do only one benchmark + // with the indicated amount of simulations. This is mostly useful to see + // memory usage as a one-off. + if let Some(arg) = std::env::args().nth(1) { + // Verify it's a number. + let n = arg.parse().expect("is a number"); + bench(n, &beam, azel, true); + std::process::exit(0); + } + + bench(1, &beam, azel, false); + bench(1, &beam, azel, false); + bench(1000, &beam, azel, false); + bench(300_000, &beam, azel, false); + bench(999_999, &beam, azel, true); +} diff --git a/comparisons/hyperbeam_example.py b/comparisons/hyperbeam_example.py new file mode 100755 index 0000000..02ff356 --- /dev/null +++ b/comparisons/hyperbeam_example.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 + +import sys +import time + +import numpy as np +import mwa_hyperbeam + + +N = 1000 +FREQ_HZ = 150e6 +DELAYS = [0] * 16 +AMPS = [1.0] * 16 + + +def get_pointings(n): + # az = np.linspace(0, 0.9 * np.pi, n) + # za = np.linspace(0.1, 0.9 * np.pi / 2, n) + az = np.ones(n) * 1.745998843813605 + za = np.ones(n) * (np.pi / 2 - 1.548676626223685) + return az, za + + +az, za = get_pointings(N) +beam = mwa_hyperbeam.FEEBeam() + +start_time = time.time() +jones = beam.calc_jones( + az[0], + za[0], + FREQ_HZ, + delays=DELAYS, + amps=AMPS, + norm_to_zenith=True, + latitude_rad=None, + iau_order=False, +) +duration = time.time() - start_time +print(f"time taken to produce 1 simulation (cold cache): {duration}s") + +start_time = time.time() +jones = beam.calc_jones( + az[0], + za[0], + FREQ_HZ, + delays=DELAYS, + amps=AMPS, + norm_to_zenith=True, + latitude_rad=None, + iau_order=False, +) +duration = time.time() - start_time +print(f"time taken to produce 1 simulation (hot cache): {duration}s") + +start_time = time.time() +jones = beam.calc_jones_array( + az, + za, + FREQ_HZ, + delays=DELAYS, + amps=AMPS, + norm_to_zenith=True, + latitude_rad=None, + iau_order=False, +) +duration = time.time() - start_time +print(f"time taken to produce {len(az)} simulations (hot cache): {duration}s") + +az, za = get_pointings(300000) +start_time = time.time() +jones = beam.calc_jones_array( + az, + za, + FREQ_HZ, + delays=DELAYS, + amps=AMPS, + norm_to_zenith=True, + latitude_rad=None, + iau_order=False, +) +duration = time.time() - start_time +print(f"time taken to produce {len(az)} simulations (hot cache): {duration}s") + +az, za = get_pointings(999999) +start_time = time.time() +jones = beam.calc_jones_array( + az, + za, + FREQ_HZ, + delays=DELAYS, + amps=AMPS, + norm_to_zenith=True, + latitude_rad=None, + iau_order=False, +) +duration = time.time() - start_time +print(f"time taken to produce {len(az)} simulations (hot cache): {duration}s") + +print("First and last MWA beam responses:") +print(jones[0]) +print(jones[-1]) + +if len(sys.argv) >= 2 and sys.argv[1] == "cuda": + print("\n*** hyperbeam Python results with CUDA ***") + start_time = time.time() + jones = beam.calc_jones_gpu( + az, + za, + [FREQ_HZ], + DELAYS, + AMPS, + norm_to_zenith=True, + latitude_rad=None, + iau_order=False, + ) + duration = time.time() - start_time + print(f"time taken to produce {len(az)} simulations (hot cache): {duration}s") diff --git a/comparisons/mwa_pb_example.py b/comparisons/mwa_pb_example.py new file mode 100755 index 0000000..c68dacb --- /dev/null +++ b/comparisons/mwa_pb_example.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 + +import time + +import numpy as np +from mwa_pb.primary_beam import MWA_Tile_full_EE + +N = 1000 +FREQ_HZ = 150e6 +DELAYS = np.array([0] * 16) + + +def get_pointings(n): + az = np.linspace(0, 0.9 * np.pi, n) + za = np.linspace(0.1, 0.9 * np.pi / 2, n) + # az = np.ones(n) * 1.745998843813605 + # za = np.ones(n) * (np.pi / 2 - 1.548676626223685) + return az, za + + +az, za = get_pointings(N) + +start_time = time.time() +jones = MWA_Tile_full_EE( + za[0], + az[0], + int(FREQ_HZ), + delays=DELAYS, + zenithnorm=True, + interp=False, + power=False, + jones=True, +) +duration = time.time() - start_time +print(f"time taken to produce 1 simulation (cold cache): {duration}s") + +start_time = time.time() +jones = MWA_Tile_full_EE( + za[0], + az[0], + int(FREQ_HZ), + delays=DELAYS, + zenithnorm=True, + interp=False, + power=False, + jones=True, +) +duration = time.time() - start_time +print(f"time taken to produce 1 simulation (hot cache): {duration}s") + +start_time = time.time() +jones = MWA_Tile_full_EE( + za, + az, + int(FREQ_HZ), + delays=DELAYS, + zenithnorm=True, + interp=False, + power=False, + jones=True, +) +duration = time.time() - start_time +print(f"time taken to produce {len(az)} simulations (hot cache): {duration}s") + +az, za = get_pointings(300000) +start_time = time.time() +jones = MWA_Tile_full_EE( + za, + az, + int(FREQ_HZ), + delays=DELAYS, + zenithnorm=True, + interp=False, + power=False, + jones=True, +) +duration = time.time() - start_time +print(f"time taken to produce {len(az)} simulations (hot cache): {duration}s") + +# Not printing these because they're different to other packages +# print("First and last MWA beam responses:") +# print(jones[0]) +# print(jones[-1]) diff --git a/comparisons/pyuvdata_example.py b/comparisons/pyuvdata_example.py new file mode 100755 index 0000000..32b883c --- /dev/null +++ b/comparisons/pyuvdata_example.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +# Written against pyuvdata 2.4.1 + +import os +import time + +import numpy as np +from pyuvdata.uvbeam import UVBeam + + +FREQ_HZ = 150e6 +DELAYS = np.zeros((2, 16), dtype=int) +AMPS = np.ones((2, 16)) + + +mwa_beam_file = os.environ.get("MWA_BEAM_FILE") + +for res in [1, 2]: + start_time = time.time() + beam = UVBeam.from_file( + mwa_beam_file, + delays=DELAYS, + amplitudes=AMPS, + frequency=[FREQ_HZ], + pixels_per_deg=res, + ) + duration = time.time() - start_time + print( + f"time taken to produce {beam.data_array.shape[-1]*beam.data_array.shape[-2]} simulations: {duration}s" + ) diff --git a/comparisons/run.sh b/comparisons/run.sh new file mode 100755 index 0000000..91518fc --- /dev/null +++ b/comparisons/run.sh @@ -0,0 +1,189 @@ +#!/bin/bash + +# This script compares a bunch of different packages' implementations of the MWA +# FEE beam code. Please file an issue if this comparison is very misleading or +# incorrect. +# +# Requirements: +# - /usr/bin/time +# - Python (with a venv module inside it) +# - everybeam (https://git.astron.nl/RD/EveryBeam) +# - a Rust toolchain (see rustup) +# +# Optional: +# - CUDA + +set -eu + +# https://stackoverflow.com/questions/4774054/reliable-way-for-a-bash-script-to-get-the-full-path-to-itself +SCRIPTPATH="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P)" + +# Set up a Python env +if [ ! -r venv ]; then + python3 -m venv venv + . ./venv/bin/activate + pip install mwa_hyperbeam everybeam mwa_pb pyuvdata +else + . ./venv/bin/activate +fi + +# Adjust to suit your system +export MWA_BEAM_FILE="${SCRIPTPATH}/$(find venv -type f -name mwa_full_embedded_element_pattern.h5)" # Automatically provided by mwa_pb +# export MWA_BEAM_FILE="/usr/local/mwa_full_embedded_element_pattern.h5" # A hard-coded path +export MS="${SCRIPTPATH}/1090008640_2s_40kHz.trunc.ms" +USE_CUDA=1 + +if [ ! -r "${MWA_BEAM_FILE}" ]; then + echo "The MWA HDF5 beam file doesn't exist; adjust the variable" + exit 1 +fi +if [ ! -r "${MS}" ]; then + # Extract the MS out of the tarball + if [ ! -r "${MS}.tar.gz" ]; then + echo "The measurement set tarball needed for EveryBeam doesn't exist; adjust the variable" + exit 1 + fi + tar -xf "${MS}.tar.gz" +fi + +##### + +run_and_measure_memory () { + local COMMAND_AND_ARGS=("$@") + + # https://stackoverflow.com/a/59592881 + { + IFS=$'\n' read -r -d '' STDERR; + IFS=$'\n' read -r -d '' STDOUT; + } < <((printf '\0%s\0' "$(/usr/bin/time -v "${COMMAND_AND_ARGS[@]}")" 1>&2) 2>&1) + + echo "${STDOUT}" + MEMORY=$(echo "${STDERR}" | grep "Maximum resident set size" | cut -d: -f2 | cut -d' ' -f2-) + echo "Max memory use (kBytes): ${MEMORY}" +} + +print_sys_info () { + echo "*** System information ***" + echo "uname -a:" + echo " $(uname -a)" + + echo "CPU:" + NAME=$(grep -m1 "model name" /proc/cpuinfo | cut -d: -f2 | cut -d' ' -f2-) + CORES=$(grep -m1 "cpu cores" /proc/cpuinfo | cut -d: -f2 | cut -d' ' -f2-) + THREADS=$(grep -m1 "siblings" /proc/cpuinfo | cut -d: -f2 | cut -d' ' -f2-) + echo " ${NAME} (${CORES} cores, ${THREADS} threads)" + + if [ $USE_CUDA == 1 ]; then + echo "GPU:" + echo " $(nvidia-smi --query-gpu=gpu_name --format=csv,noheader)" + fi + + echo "Total memory:" + echo " $(free -m | grep -m1 "Mem:" | sed -e 's|\(Mem:\s\+\)\([0-9]\+\)\(.*\)|\2|') MiB" + + local GLIBC=/usr/lib/libc.so.6 + if [ -r $GLIBC ]; then + echo "glibc:" + echo " $($GLIBC | head -n1)" + fi + + echo "Compilers:" + echo " GCC: $(g++ --version | head -n1)" + echo " Rust: $(rustc --version)" + if [ $USE_CUDA == 1 ]; then + echo " nvcc: $(nvcc --version | grep release)" + fi + + echo "Python:" + echo " $(python --version)" + + echo "***" + echo "" +} + +run_mwa_pb_python () { + echo "*** mwa_pb Python results ***" + run_and_measure_memory ./mwa_pb_example.py + echo "***" + echo "" +} + +run_everybeam_python () { + echo "*** EveryBeam Python results ***" + run_and_measure_memory ./everybeam_example.py + echo "***" + echo "" +} + +run_pyuvdata_python () { + echo "*** pyuvdata Python results ***" + run_and_measure_memory ./pyuvdata_example.py + echo "***" + echo "" +} + +run_hyperbeam_python () { + echo "*** hyperbeam Python results with 1 CPU core ***" + export RAYON_NUM_THREADS=1 + run_and_measure_memory ./hyperbeam_example.py + unset RAYON_NUM_THREADS + + echo "" + echo "*** hyperbeam Python results with all CPU cores ***" + if [ $USE_CUDA == 1 ]; then + run_and_measure_memory ./hyperbeam_example.py cuda + else + run_and_measure_memory ./hyperbeam_example.py + fi + + echo "***" + echo "" +} + +run_everybeam_cpp () { + echo "*** Compiling EveryBeam C++ example ***" + make + echo "" + echo "*** EveryBeam C++ results with 1 CPU core ***" + run_and_measure_memory ./everybeam_example "${MS}" + echo "***" + echo "" +} + +run_hyperbeam_rust () { + echo "*** Compiling hyperbeam Rust code ***" + if [ $USE_CUDA == 1 ]; then + cargo build --release --features=cuda,gpu-single + else + cargo build --release + fi + echo "" + + echo "*** hyperbeam Rust results with 1 CPU core ***" + export RAYON_NUM_THREADS=1 + run_and_measure_memory ./target/release/hyperbeam + unset RAYON_NUM_THREADS + echo "" + + echo "*** hyperbeam Rust results with all CPU cores ***" + run_and_measure_memory ./target/release/hyperbeam + + if [ $USE_CUDA == 1 ]; then + echo "" + echo "*** hyperbeam Rust results with CUDA ***" + run_and_measure_memory ./target/release/hyperbeam-cuda + fi + + echo "***" + echo "" +} + +print_sys_info +run_mwa_pb_python +# run_everybeam_python # MWA not supported +run_pyuvdata_python +run_hyperbeam_python +cd everybeam +run_everybeam_cpp +cd ../hyperbeam +run_hyperbeam_rust