Skip to content

Commit

Permalink
Add code for the analytic beam.
Browse files Browse the repository at this point in the history
mwa_pb and the RTS disagree on how the beam is implemented, so both
versions are available. You're welcome.

A future release should rename the FEE FFI functions to be unambiguously to do
with FEE (e.g. calc_jones -> fee_calc_jones). This is not done here so we don't
break semver.

The `mut` requirement on `self` for python functions has been
removed. Similarly, the FEE FFI beam is no longer accessed as mutable. I'm
pretty sure this doesn't change anything.
  • Loading branch information
cjordan committed Oct 30, 2023
1 parent 121e392 commit c97d584
Show file tree
Hide file tree
Showing 43 changed files with 4,647 additions and 448 deletions.
27 changes: 23 additions & 4 deletions .github/workflows/run-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,24 @@ jobs:
gcc -O3 examples/fee.c -o fee -I ./include/ -L ./target/debug/ -l mwa_hyperbeam
gcc -O3 examples/fee_parallel.c -o fee_parallel -I ./include/ -L ./target/debug/ -l mwa_hyperbeam
gcc -O3 -fopenmp examples/fee_parallel_omp.c -o fee_parallel_omp -I ./include/ -L ./target/debug/ -l mwa_hyperbeam
gcc -O3 examples/get_freqs.c -o get_freqs -I ./include/ -L ./target/debug/ -l mwa_hyperbeam
gcc -O3 examples/fee_get_freqs.c -o fee_get_freqs -I ./include/ -L ./target/debug/ -l mwa_hyperbeam
gcc -O3 examples/analytic.c -o analytic -I ./include/ -L ./target/debug/ -l mwa_hyperbeam
gcc -O3 examples/analytic_parallel.c -o analytic_parallel -I ./include/ -L ./target/debug/ -l mwa_hyperbeam
else
/usr/local/bin/gcc-12 -O3 examples/fee.c -o fee -I ./include/ -L ./target/debug/ -l mwa_hyperbeam
/usr/local/bin/gcc-12 -O3 examples/fee_parallel.c -o fee_parallel -I ./include/ -L ./target/debug/ -l mwa_hyperbeam
/usr/local/bin/gcc-12 -O3 -fopenmp examples/fee_parallel_omp.c -o fee_parallel_omp -I ./include/ -L ./target/debug/ -l mwa_hyperbeam
/usr/local/bin/gcc-12 -O3 examples/get_freqs.c -o get_freqs -I ./include/ -L ./target/debug/ -l mwa_hyperbeam
/usr/local/bin/gcc-12 -O3 examples/fee_get_freqs.c -o fee_get_freqs -I ./include/ -L ./target/debug/ -l mwa_hyperbeam
/usr/local/bin/gcc-12 -O3 examples/analytic.c -o analytic -I ./include/ -L ./target/debug/ -l mwa_hyperbeam
/usr/local/bin/gcc-12 -O3 examples/analytic_parallel.c -o analytic_parallel -I ./include/ -L ./target/debug/ -l mwa_hyperbeam
fi
echo "*** Running C examples ***"
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/debug/ ./fee mwa_full_embedded_element_pattern.h5
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/debug/ ./fee_parallel mwa_full_embedded_element_pattern.h5
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/debug/ ./fee_parallel_omp mwa_full_embedded_element_pattern.h5
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/debug/ ./get_freqs mwa_full_embedded_element_pattern.h5
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/debug/ ./fee_get_freqs mwa_full_embedded_element_pattern.h5
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/debug/ ./analytic
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/debug/ ./analytic_parallel
- name: Run Python example
run: |
Expand All @@ -90,8 +96,9 @@ jobs:
pip install numpy maturin
echo "*** Compiling Python hyperbeam ***"
maturin develop -b pyo3 --features=python
echo "*** Running Python example ***"
echo "*** Running Python examples ***"
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/debug/ ./examples/fee.py mwa_full_embedded_element_pattern.h5
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/debug/ ./examples/analytic.py
- name: CUDA examples
if: runner.os == 'Linux'
Expand All @@ -105,21 +112,33 @@ jobs:
# Examples may fail because there's no GPU
cargo run --features=cuda --example fee_cuda 10
cargo run --features=cuda --example analytic_cuda 10
cargo run --features=cuda,gpu-single --example fee_cuda 10
cargo run --features=cuda,gpu-single --example analytic_cuda 10
cargo build --release --features=cuda
gcc -O3 examples/fee_gpu.c -o fee_cuda -I ./include/ -L ./target/release/ -l mwa_hyperbeam
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/release/ ./fee_cuda mwa_full_embedded_element_pattern.h5
nvcc -O3 examples/fee_cuda_device.cu -o fee_cuda_device -I ./include/ -L ./target/release/ -l mwa_hyperbeam
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/release ./fee_cuda_device mwa_full_embedded_element_pattern.h5
gcc -O3 examples/analytic_gpu.c -o analytic_cuda -I ./include/ -L ./target/release/ -l mwa_hyperbeam
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/release/ ./analytic_cuda
nvcc -O3 examples/analytic_cuda_device.cu -o analytic_cuda_device -I ./include/ -L ./target/release/ -l mwa_hyperbeam
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/release ./analytic_cuda_device
cargo build --release --features=cuda,gpu-single
gcc -O3 -D SINGLE examples/fee_gpu.c -o fee_cuda -I ./include/ -L ./target/release/ -l mwa_hyperbeam
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/release/ ./fee_cuda mwa_full_embedded_element_pattern.h5
nvcc -O3 -D SINGLE examples/fee_cuda_device.cu -o fee_cuda_device -I ./include/ -L ./target/release/ -l mwa_hyperbeam
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/release ./fee_cuda_device mwa_full_embedded_element_pattern.h5
gcc -O3 -D SINGLE examples/analytic_gpu.c -o analytic_cuda -I ./include/ -L ./target/release/ -l mwa_hyperbeam
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/release/ ./analytic_cuda
nvcc -O3 -D SINGLE examples/analytic_cuda_device.cu -o analytic_cuda_device -I ./include/ -L ./target/release/ -l mwa_hyperbeam
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/release ./analytic_cuda_device
maturin develop -b pyo3 --features=python,cuda
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/release/ ./examples/fee_gpu.py mwa_full_embedded_element_pattern.h5
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/release/ ./examples/analytic_gpu.py
maturin develop -b pyo3 --features=python,cuda,gpu-single
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/release/ ./examples/fee_gpu.py mwa_full_embedded_element_pattern.h5
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./target/release/ ./examples/analytic_gpu.py
4 changes: 2 additions & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ jobs:
cargo fmt --all -- --check
cargo clippy --all -- -D warnings
- name: No CUDA references in CPU-only header
- name: No GPU references in CPU-only header
run: |
! grep -i cuda include/mwa_hyperbeam.h
! grep -i gpu include/mwa_hyperbeam.h
- name: Get the HDF5 file
run: curl http://ws.mwatelescope.org/static/mwa_full_embedded_element_pattern.h5 -o mwa_full_embedded_element_pattern.h5
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0.html).

## [0.6.1] - 2023-10-31
### Added
- The "analytic" MWA beam
- There are (unfortunately) two flavours -- "RTS" and "mwa_pb". Both are
supported.

## [0.6.0] - 2023-09-14
### Added
- Support for compiling GPU code with HIP
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mwa_hyperbeam"
version = "0.6.0"
version = "0.6.1"
authors = [
"Christopher H. Jordan <christopherjordan87@gmail.com>",
"Jack L. B. Line <jack.line@curtin.edu.au>",
Expand Down Expand Up @@ -92,6 +92,14 @@ required-features = ["cuda"]
name = "fee_hip"
required-features = ["hip"]

[[example]]
name = "analytic_cuda"
required-features = ["cuda"]

[[example]]
name = "analytic_hip"
required-features = ["hip"]

[patch.crates-io]
# marlu = { path = "../Marlu" }
# marlu = { git = "https://github.com/MWATelescope/Marlu", branch = "DUT1" }
20 changes: 17 additions & 3 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ fn main() {
"get_num_unique_tiles".to_string(),
"get_num_unique_fee_freqs".to_string(),
"free_gpu_fee_beam".to_string(),
"AnalyticBeamGpu".to_string(),
"new_gpu_analytic_beam".to_string(),
"analytic_calc_jones_gpu".to_string(),
"analytic_calc_jones_gpu_device".to_string(),
"analytic_calc_jones_gpu_device_inner".to_string(),
"get_analytic_tile_map".to_string(),
"get_analytic_device_tile_map".to_string(),
"get_num_unique_analytic_tiles".to_string(),
"free_gpu_analytic_beam".to_string(),
],
..Default::default()
};
Expand Down Expand Up @@ -166,11 +175,14 @@ mod gpu {
cuda_target
.cuda(true)
.cudart("shared") // We handle linking cudart statically
.include("src/gpu_common/")
.include("src/fee/gpu/")
.file("src/fee/gpu/fee.cu");
.file("src/fee/gpu/fee.cu")
.include("src/analytic/gpu/")
.file("src/analytic/gpu/analytic.cu");
// If $CXX is not set but $CUDA_PATH is, search for
// $CUDA_PATH/bin/g++ and if it exists, set that as $CXX.
if !env::var_os("CXX").is_some() {
if env::var_os("CXX").is_none() {
// Unlike above, we care about $CUDA_PATH being unicode.
if let Ok(cuda_path) = env::var("CUDA_PATH") {
// Look for the g++ that CUDA wants.
Expand Down Expand Up @@ -217,7 +229,9 @@ mod gpu {
hip_target
.compiler(compiler)
.include(hip_path.join("include/hip"))
.file("src/fee/gpu/fee.cu");
.include("src/gpu_common/")
.file("src/fee/gpu/fee.cu")
.file("src/analytic/gpu/analytic.cu");

hip_target
};
Expand Down
89 changes: 89 additions & 0 deletions examples/analytic.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Build and run with something like:
// gcc -O3 -I ../include/ -L ../target/release/ -l mwa_hyperbeam ./analytic.c -o analytic
// LD_LIBRARY_PATH=../target/release ./analytic

#include <complex.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include "mwa_hyperbeam.h"

void handle_hyperbeam_error(char file[], int line_num, const char function_name[]) {
int err_length = hb_last_error_length();
char *err = malloc(err_length * sizeof(char));
int err_status = hb_last_error_message(err, err_length);
if (err_status == -1) {
printf("Something really bad happened!\n");
exit(EXIT_FAILURE);
}
printf("File %s:%d: hyperbeam error in %s: %s\n", file, line_num, function_name, err);

exit(EXIT_FAILURE);
}

int main(int argc, char *argv[]) {
// Get a new beam object from hyperbeam.
AnalyticBeam *beam;
char rts_style = 0; // 1 or RTS style, 0 for mwa_pb
double *dipole_height_metres = NULL; // Point to a valid float if you want a custom height
if (new_analytic_beam(rts_style, dipole_height_metres, &beam))
handle_hyperbeam_error(__FILE__, __LINE__, "new_analytic_beam");

// Set up the direction and pointing to test.
double az = 45.0 * M_PI / 180.0;
double za = 80.0 * M_PI / 180.0;
// Delays and amps correspond to dipoles in the "M&C order". See
// https://wiki.mwatelescope.org/pages/viewpage.action?pageId=48005139) for
// more info. Amps refer to dipole gains, and are usually set to 1 or 0 (if
// a dipole is dead).
unsigned delays[16] = {3, 2, 1, 0, 3, 2, 1, 0, 3, 2, 1, 0, 3, 2, 1, 0};
double amps[16] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1};
int freq_hz = 51200000;
// MWA latitude
double latitude_rad = -0.4660608448386394;
// Should we normalise the beam response?
int norm_to_zenith = 1;

// Calculate the Jones matrix for this direction and pointing. This Jones
// matrix is on the stack.
complex double jones[4];
// hyperbeam expects a pointer to doubles. Casting the pointer works fine.
if (analytic_calc_jones(beam, az, za, freq_hz, delays, amps, 16, latitude_rad, norm_to_zenith, (double *)&jones))
handle_hyperbeam_error(__FILE__, __LINE__, "analytic_calc_jones");

printf("The returned Jones matrix:\n");
printf("[[%+.8f%+.8fi,", creal(jones[0]), cimag(jones[0]));
printf(" %+.8f%+.8fi]\n", creal(jones[1]), cimag(jones[1]));
printf(" [%+.8f%+.8fi,", creal(jones[2]), cimag(jones[2]));
printf(" %+.8f%+.8fi]]\n", creal(jones[3]), cimag(jones[3]));

// Amps can have 32 elements to specify amps of the X and Y dipoles. The
// first 16 elements are X amps, the second 16 are Y amps.
double amps_2[32] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
// This Jones matrix is on the heap.
complex double *jones_2 = malloc(4 * sizeof(complex double));
if (analytic_calc_jones(beam, az, za, freq_hz, delays, amps_2, 32, latitude_rad, norm_to_zenith, (double *)jones_2))
handle_hyperbeam_error(__FILE__, __LINE__, "analytic_calc_jones");

// The resulting Jones matrix has different elements on the second row,
// corresponding to the Y element; this is because we only altered the Y
// amps.
printf("The returned Jones matrix with altered Y amps:\n");
printf("[[%+.8f%+.8fi,", creal(jones_2[0]), cimag(jones_2[0]));
printf(" %+.8f%+.8fi]\n", creal(jones_2[1]), cimag(jones_2[1]));
printf(" [%+.8f%+.8fi,", creal(jones_2[2]), cimag(jones_2[2]));
printf(" %+.8f%+.8fi]]\n", creal(jones_2[3]), cimag(jones_2[3]));

// Free the heap-allocated Jones matrix.
free(jones_2);
// Free the beam - we must use a special function to do this.
free_analytic_beam(beam);

return EXIT_SUCCESS;
}
48 changes: 48 additions & 0 deletions examples/analytic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env python

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import sys
import time
import numpy as np
import mwa_hyperbeam

if len(sys.argv) > 1 and sys.argv[1] == "rts":
beam = mwa_hyperbeam.AnalyticBeam(rts_behaviour=True)
else:
beam = mwa_hyperbeam.AnalyticBeam()

# Make a lot of directions for hyperbeam to calculate in parallel.
n = 1000000
az = np.linspace(0, 0.9 * np.pi, n)
za = np.linspace(0.1, 0.9 * np.pi / 2, n)
freq = 167000000
# Delays and amps correspond to dipoles in the "M&C order". See
# https://wiki.mwatelescope.org/pages/viewpage.action?pageId=48005139) for more
# info.
delays = [0] * 16
amps = [1.0] * 16
# Should we normalise the beam response?
norm_to_zenith = True

# Pass the values to hyperbeam and get a numpy array back. Each element is a
# 4-element Jones matrix.
start_time = time.time()
# beam.calc_jones is also available, but that makes a single Jones matrix at a
# time, so one would need to iterate over az and za. calc_jones_array is done in
# parallel with Rust (so it's fast).
jones = beam.calc_jones_array(az, za, freq, delays, amps, norm_to_zenith)
duration = time.time() - start_time
print("Time to calculate {} directions: {:.3}s".format(n, duration))
print("First Jones matrix:")
print(jones[0])

# It's also possible to supply amps for all dipole elements. The first 16 amps
# are for X elements, the second 16 are for Y elements.
amps = np.ones(32)
amps[-1] = 0
jones = beam.calc_jones_array(az[:1], za[:1], freq, delays, amps, norm_to_zenith)
print("First Jones matrix with altered Y amps:")
print(jones[0])
77 changes: 77 additions & 0 deletions examples/analytic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

//! Example of using hyperbeam analytic beam code with Rust.
//!
//! Build and run with something like:
//! `cargo run --release --example analytic -- 10000`
//!
//! If you want to use hyperbeam in your own Rust crate, then check out the latest
//! version on crates.io:
//!
//! https://crates.io/crates/mwa_hyperbeam

use std::f64::consts::{FRAC_PI_2, PI};

use mwa_hyperbeam::{analytic::AnalyticBeam, AzEl};

fn main() {
if let Err(e) = try_main() {
eprintln!("Error: {e}");
std::process::exit(1);
}
}

fn try_main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = std::env::args().skip(1);
let num_directions: usize = args
.next()
.expect("number of directions supplied")
.parse()
.expect("number of directions is a number");

// `AnalyticBeam::new` gives the default beam (in this case, matching
// `mwa_pb` behaviour), but `AnalyticBeam::new_rts` also exists.
let beam = AnalyticBeam::new();

// Set up the directions to test.
let mut azels = vec![];
for i in 0..num_directions {
let az = 0.9 * PI * i as f64 / num_directions as f64;
let za = 0.1 + 0.9 * PI / 2.0 * i as f64 / num_directions as f64;
azels.push(AzEl::from_radians(az, FRAC_PI_2 - za));
}
let freq_hz = 51200000;
// Delays and amps correspond to dipoles in the "M&C order". See
// https://wiki.mwatelescope.org/pages/viewpage.action?pageId=48005139) for
// more info.
let delays = [0; 16];
assert_eq!(delays.len(), 16);
let amps = [1.0; 16];
assert!(amps.len() == 16 || amps.len() == 32);
let latitude_rad = -0.4660608448386394; // MWA
let norm_to_zenith = true;

// Call hyperbeam.
let jones = beam.calc_jones_array(
&azels,
freq_hz,
&delays,
&amps,
latitude_rad,
norm_to_zenith,
)?;
println!("The first Jones matrix:");
// This works, but the formatting for this isn't very pretty.
// println!("{}", jones[0]);

// For demonstrations' sake, this gives easier-to-read output.
let j = jones[0];
println!(
"[[{:+.8}, {:+.8}]\n [{:+.8}, {:+.8}]]",
j[0], j[1], j[2], j[3]
);

Ok(())
}
Loading

0 comments on commit c97d584

Please sign in to comment.