예제 #1
0
int main(int argc, char** argv)
{
    int status = 0;
    oskar::OptionParser opt("oskar_convert_geodetic_to_ecef",
            oskar_version_string());
    opt.set_description("Converts geodetic longitude/latitude/altitude to "
            "Cartesian ECEF coordinates. Assumes WGS84 ellipsoid.");
    opt.add_required("input file", "Path to file containing input coordinates. "
            "Angles must be in degrees.");
    if (!opt.check_options(argc, argv))
        return OSKAR_FAIL;
    const char* filename = opt.get_arg();

    // Load the input file.
    oskar_Mem *lon = oskar_mem_create(OSKAR_DOUBLE, OSKAR_CPU, 0, &status);
    oskar_Mem *lat = oskar_mem_create(OSKAR_DOUBLE, OSKAR_CPU, 0, &status);
    oskar_Mem *alt = oskar_mem_create(OSKAR_DOUBLE, OSKAR_CPU, 0, &status);
    size_t num_points = oskar_mem_load_ascii(filename, 3, &status,
            lon, "", lat, "", alt, "0.0");
    oskar_mem_scale_real(lon, M_PI / 180.0, &status);
    oskar_mem_scale_real(lat, M_PI / 180.0, &status);

    // Convert coordinates.
    oskar_Mem *x = oskar_mem_create(OSKAR_DOUBLE, OSKAR_CPU,
            num_points, &status);
    oskar_Mem *y = oskar_mem_create(OSKAR_DOUBLE, OSKAR_CPU,
            num_points, &status);
    oskar_Mem *z = oskar_mem_create(OSKAR_DOUBLE, OSKAR_CPU,
            num_points, &status);
    oskar_convert_geodetic_spherical_to_ecef(num_points,
            oskar_mem_double_const(lon, &status),
            oskar_mem_double_const(lat, &status),
            oskar_mem_double_const(alt, &status),
            oskar_mem_double(x, &status),
            oskar_mem_double(y, &status),
            oskar_mem_double(z, &status));

    // Print converted coordinates.
    oskar_mem_save_ascii(stdout, 3, num_points, &status, x, y, z);

    // Clean up.
    oskar_mem_free(lon, &status);
    oskar_mem_free(lat, &status);
    oskar_mem_free(alt, &status);
    oskar_mem_free(x, &status);
    oskar_mem_free(y, &status);
    oskar_mem_free(z, &status);
    if (status)
    {
        oskar_log_error(0, oskar_get_error_string(status));
        return status;
    }

    return 0;
}
예제 #2
0
파일: oskar_fft.c 프로젝트: OxfordSKA/OSKAR
void oskar_fft_exec(oskar_FFT* h, oskar_Mem* data, int* status)
{
    oskar_Mem *data_copy = 0, *data_ptr = data;
    if (oskar_mem_location(data) != h->location)
    {
        data_copy = oskar_mem_create_copy(data, h->location, status);
        data_ptr = data_copy;
    }
    if (h->location == OSKAR_CPU)
    {
        if (h->num_dim == 1)
        {
            *status = OSKAR_ERR_FUNCTION_NOT_AVAILABLE;
        }
        else if (h->num_dim == 2)
        {
            if (h->precision == OSKAR_DOUBLE)
                oskar_fftpack_cfft2f(h->dim_size, h->dim_size, h->dim_size,
                        oskar_mem_double(data_ptr, status),
                        oskar_mem_double(h->fftpack_wsave, status),
                        oskar_mem_double(h->fftpack_work, status));
            else
                oskar_fftpack_cfft2f_f(h->dim_size, h->dim_size, h->dim_size,
                        oskar_mem_float(data_ptr, status),
                        oskar_mem_float(h->fftpack_wsave, status),
                        oskar_mem_float(h->fftpack_work, status));
            /* This step not needed for W-kernel generation, so turn it off. */
            if (h->ensure_consistent_norm)
                oskar_mem_scale_real(data_ptr, (double)h->num_cells_total,
                        0, h->num_cells_total, status);
        }
    }
    else if (h->location == OSKAR_GPU)
    {
#ifdef OSKAR_HAVE_CUDA
        if (h->precision == OSKAR_DOUBLE)
            cufftExecZ2Z(h->cufft_plan,
                    (cufftDoubleComplex*) oskar_mem_void(data_ptr),
                    (cufftDoubleComplex*) oskar_mem_void(data_ptr),
                    CUFFT_FORWARD);
        else
            cufftExecC2C(h->cufft_plan,
                    (cufftComplex*) oskar_mem_void(data_ptr),
                    (cufftComplex*) oskar_mem_void(data_ptr),
                    CUFFT_FORWARD);
#endif
    }
    else
        *status = OSKAR_ERR_BAD_LOCATION;
    if (oskar_mem_location(data) != h->location)
        oskar_mem_copy(data, data_ptr, status);
    oskar_mem_free(data_copy, status);
}
예제 #3
0
void oskar_imager_finalise(oskar_Imager* h,
        int num_output_images, oskar_Mem** output_images,
        int num_output_grids, oskar_Mem** output_grids, int* status)
{
    int t, c, p, i;
    if (*status || !h->planes) return;

    /* Adjust normalisation if required. */
    if (h->scale_norm_with_num_input_files)
    {
        for (i = 0; i < h->num_planes; ++i)
            h->plane_norm[i] /= h->num_files;
    }

    /* Copy grids to output grid planes if given. */
    for (i = 0; (i < h->num_planes) && (i < num_output_grids); ++i)
    {
        oskar_mem_copy(output_grids[i], h->planes[i], status);
        oskar_mem_scale_real(output_grids[i], 1.0 / h->plane_norm[i], status);
    }

    /* Check if images are required. */
    if (h->fits_file[0] || output_images)
    {
        /* Finalise all the planes. */
        for (i = 0; i < h->num_planes; ++i)
        {
            oskar_imager_finalise_plane(h,
                    h->planes[i], h->plane_norm[i], status);
            oskar_imager_trim_image(h->planes[i],
                    h->grid_size, h->image_size, status);
        }

        /* Copy images to output image planes if given. */
        for (i = 0; (i < h->num_planes) && (i < num_output_images); ++i)
        {
            memcpy(oskar_mem_void(output_images[i]),
                    oskar_mem_void_const(h->planes[i]), h->image_size *
                    h->image_size * oskar_mem_element_size(h->imager_prec));
        }

        /* Write to files if required. */
        for (t = 0, i = 0; t < h->im_num_times; ++t)
            for (c = 0; c < h->im_num_channels; ++c)
                for (p = 0; p < h->im_num_pols; ++p, ++i)
                    write_plane(h, h->planes[i], t, c, p, status);
    }

    /* Reset imager memory. */
    oskar_imager_reset_cache(h, status);
}
예제 #4
0
void oskar_evaluate_station_beam(oskar_Mem* beam_pattern, int num_points,
        int coord_type, oskar_Mem* x, oskar_Mem* y, oskar_Mem* z,
        double norm_ra_rad, double norm_dec_rad, const oskar_Station* station,
        oskar_StationWork* work, int time_index, double frequency_hz,
        double GAST, int* status)
{
    int normalise_final_beam;
    oskar_Mem* out;

    /* Check if safe to proceed. */
    if (*status) return;

    /* Set default output beam array. */
    out = beam_pattern;

    /* Check that the arrays have enough space to add an extra source at the
     * end (for normalisation). We don't want to reallocate here, since that
     * will be slow to do each time: must simply ensure that we pass input
     * arrays that are large enough.
     * The normalisation doesn't need to happen if the station has an
     * isotropic beam. */
    normalise_final_beam = oskar_station_normalise_final_beam(station) &&
            (oskar_station_type(station) != OSKAR_STATION_TYPE_ISOTROPIC);
    if (normalise_final_beam)
    {
        double c_x = 0.0, c_y = 0.0, c_z = 1.0;

        /* Increment number of points. */
        num_points++;

        /* Check the input arrays are big enough to hold the new source. */
        if ((int)oskar_mem_length(x) < num_points ||
                (int)oskar_mem_length(y) < num_points ||
                (int)oskar_mem_length(z) < num_points)
        {
            *status = OSKAR_ERR_DIMENSION_MISMATCH;
            return;
        }

        /* Set output beam array to work buffer. */
        out = oskar_station_work_normalised_beam(work, beam_pattern, status);

        /* Get the beam direction in the appropriate coordinate system. */
        /* (Direction cosines are already set to the interferometer phase
         * centre for relative directions.) */
        if (coord_type == OSKAR_ENU_DIRECTIONS)
        {
            double t_x, t_y, t_z, ha0;
            ha0 = (GAST + oskar_station_lon_rad(station)) - norm_ra_rad;
            oskar_convert_relative_directions_to_enu_directions_d(
                    &t_x, &t_y, &t_z, 1, &c_x, &c_y, &c_z, ha0, norm_dec_rad,
                    oskar_station_lat_rad(station));
            c_x = t_x;
            c_y = t_y;
            c_z = t_z;
        }

        /* Add the extra normalisation source to the end of the arrays. */
        oskar_mem_set_element_real(x, num_points-1, c_x, status);
        oskar_mem_set_element_real(y, num_points-1, c_y, status);
        oskar_mem_set_element_real(z, num_points-1, c_z, status);
    }

    /* Evaluate the station beam for the given directions. */
    if (coord_type == OSKAR_ENU_DIRECTIONS)
    {
        evaluate_station_beam_enu_directions(out, num_points, x, y, z,
                station, work, time_index, frequency_hz, GAST, status);
    }
    else if (coord_type == OSKAR_RELATIVE_DIRECTIONS)
    {
        evaluate_station_beam_relative_directions(out, num_points, x, y, z,
                station, work, time_index, frequency_hz, GAST, status);
    }
    else
    {
        *status = OSKAR_ERR_INVALID_ARGUMENT;
    }

    /* Scale beam pattern by value of the last source if required. */
    if (normalise_final_beam)
    {
        double amp = 0.0;

        /* Get the last element of the vector and convert to amplitude. */
        if (oskar_mem_is_matrix(out))
        {
            double4c val;
            val = oskar_mem_get_element_matrix(out, num_points-1, status);

            /*
             * Scale by square root of "Stokes I" autocorrelation:
             * sqrt(0.5 * [sum of resultant diagonal]).
             *
             * We have
             * [ Xa  Xb ] [ Xa*  Xc* ] = [ Xa Xa* + Xb Xb*    (don't care)   ]
             * [ Xc  Xd ] [ Xb*  Xd* ]   [  (don't care)     Xc Xc* + Xd Xd* ]
             *
             * Stokes I is completely real, so need only evaluate the real
             * part of all the multiplies. Because of the conjugate terms,
             * these become re*re + im*im.
             *
             * Need the square root because we only want the normalised value
             * for the beam itself (in isolation), not its actual
             * autocorrelation!
             */
            amp = val.a.x * val.a.x + val.a.y * val.a.y +
                    val.b.x * val.b.x + val.b.y * val.b.y +
                    val.c.x * val.c.x + val.c.y * val.c.y +
                    val.d.x * val.d.x + val.d.y * val.d.y;
            amp = sqrt(0.5 * amp);
        }
        else
        {
            double2 val;
            val = oskar_mem_get_element_complex(out, num_points-1, status);

            /* Scale by voltage. */
            amp = sqrt(val.x * val.x + val.y * val.y);
        }

        /* Scale beam array by normalisation value. */
        oskar_mem_scale_real(out, 1.0/amp, status);

        /* Copy output beam data. */
        oskar_mem_copy_contents(beam_pattern, out, 0, 0, num_points-1, status);
    }
}
void oskar_convert_brightness_to_jy(oskar_Mem* data, double beam_area_pixels,
        double pixel_area_sr, double frequency_hz, double min_peak_fraction,
        double min_abs_val, const char* reported_map_units,
        const char* default_map_units, int override_input_units, int* status)
{
    static const double k_B = 1.3806488e-23; /* Boltzmann constant. */
    double lambda, peak = 0.0, peak_min = 0.0, scaling = 1.0, val = 0.0;
    const char* unit_str = 0;
    int i = 0, num_pixels, units = 0, type;
    if (*status) return;

    /* Filter and find peak of image. */
    num_pixels = (int) oskar_mem_length(data);
    type = oskar_mem_precision(data);
    if (type == OSKAR_SINGLE)
    {
        float *img = oskar_mem_float(data, status);
        for (i = 0; i < num_pixels; ++i)
        {
            val = img[i];
            if (val > peak) peak = val;
        }
        peak_min = peak * min_peak_fraction;
        for (i = 0; i < num_pixels; ++i)
        {
            val = img[i];
            if (val < peak_min || val < min_abs_val) img[i] = 0.0;
        }
    }
    else
    {
        double *img = oskar_mem_double(data, status);
        for (i = 0; i < num_pixels; ++i)
        {
            val = img[i];
            if (val > peak) peak = val;
        }
        peak_min = peak * min_peak_fraction;
        for (i = 0; i < num_pixels; ++i)
        {
            val = img[i];
            if (val < peak_min || val < min_abs_val) img[i] = 0.0;
        }
    }

    /* Find brightness units. */
    unit_str = (!reported_map_units || override_input_units) ?
            default_map_units : reported_map_units;
    if (!strncmp(unit_str, "JY/BEAM", 7) ||
            !strncmp(unit_str, "Jy/beam", 7))
        units = JY_BEAM;
    else if (!strncmp(unit_str, "JY/PIXEL", 8) ||
            !strncmp(unit_str, "Jy/pixel", 8))
        units = JY_PIXEL;
    else if (!strncmp(unit_str, "mK", 2))
        units = MK;
    else if (!strncmp(unit_str, "K", 1))
        units = K;
    else
        *status = OSKAR_ERR_BAD_UNITS;

    /* Check if we need to convert the pixel values. */
    if (units == JY_BEAM)
    {
        if (beam_area_pixels == 0.0)
        {
            oskar_log_error("Need beam area for maps in Jy/beam.");
            *status = OSKAR_ERR_BAD_UNITS;
        }
        else
            scaling = 1.0 / beam_area_pixels;
    }
    else if (units == K || units == MK)
    {
        if (units == MK)
            scaling = 1e-3; /* Convert milli-Kelvin to Kelvin. */

        /* Convert temperature to Jansky per pixel. */
        /* Brightness temperature to flux density conversion:
         * http://www.iram.fr/IRAMFR/IS/IS2002/html_1/node187.html */
        /* Multiply by 2.0 * k_B * pixel_area * 10^26 / lambda^2. */
        lambda = 299792458.0 / frequency_hz;
        scaling *= (2.0 * k_B * pixel_area_sr * 1e26 / (lambda*lambda));
    }

    /* Scale pixels into Jy. */
    if (scaling != 1.0)
        oskar_mem_scale_real(data, scaling, 0, oskar_mem_length(data), status);
}
예제 #6
0
void oskar_imager_finalise_plane(oskar_Imager* h, oskar_Mem* plane,
        double plane_norm, int* status)
{
    int size, num_cells;
    DeviceData* d;
    if (*status) return;

    /* Apply normalisation. */
    if (plane_norm > 0.0 || plane_norm < 0.0)
        oskar_mem_scale_real(plane, 1.0 / plane_norm, status);
    if (h->algorithm == OSKAR_ALGORITHM_DFT_2D ||
            h->algorithm == OSKAR_ALGORITHM_DFT_3D)
        return;

    /* Check plane is complex type, as plane must be gridded visibilities. */
    if (!oskar_mem_is_complex(plane))
    {
        *status = OSKAR_ERR_TYPE_MISMATCH;
        return;
    }

    /* Make image using FFT and apply grid correction. */
    size = h->grid_size;
    num_cells = size * size;
    d = &h->d[0];
    if (oskar_mem_precision(plane) == OSKAR_DOUBLE)
    {
        oskar_fftphase_cd(size, size, oskar_mem_double(plane, status));
        if (h->fft_on_gpu)
        {
#ifdef OSKAR_HAVE_CUDA
            oskar_device_set(h->cuda_device_ids[0], status);
            oskar_mem_copy(d->plane_gpu, plane, status);
            cufftExecZ2Z(h->cufft_plan, oskar_mem_void(d->plane_gpu),
                    oskar_mem_void(d->plane_gpu), CUFFT_FORWARD);
            oskar_mem_copy(plane, d->plane_gpu, status);
#else
            *status = OSKAR_ERR_CUDA_NOT_AVAILABLE;
#endif
        }
        else
        {
            oskar_fftpack_cfft2f(size, size, size,
                    oskar_mem_double(plane, status),
                    oskar_mem_double(h->fftpack_wsave, status),
                    oskar_mem_double(h->fftpack_work, status));
            oskar_mem_scale_real(plane, (double)num_cells, status);
        }
        oskar_fftphase_cd(size, size, oskar_mem_double(plane, status));
        oskar_grid_correction_d(size, oskar_mem_double(h->corr_func, status),
                oskar_mem_double(plane, status));
    }
    else
    {
        oskar_fftphase_cf(size, size, oskar_mem_float(plane, status));
        if (h->fft_on_gpu)
        {
#ifdef OSKAR_HAVE_CUDA
            oskar_device_set(h->cuda_device_ids[0], status);
            oskar_mem_copy(d->plane_gpu, plane, status);
            cufftExecC2C(h->cufft_plan, oskar_mem_void(d->plane_gpu),
                    oskar_mem_void(d->plane_gpu), CUFFT_FORWARD);
            oskar_mem_copy(plane, d->plane_gpu, status);
#else
            *status = OSKAR_ERR_CUDA_NOT_AVAILABLE;
#endif
        }
        else
        {
            oskar_fftpack_cfft2f_f(size, size, size,
                    oskar_mem_float(plane, status),
                    oskar_mem_float(h->fftpack_wsave, status),
                    oskar_mem_float(h->fftpack_work, status));
            oskar_mem_scale_real(plane, (double)num_cells, status);
        }
        oskar_fftphase_cf(size, size, oskar_mem_float(plane, status));
        oskar_grid_correction_f(size, oskar_mem_double(h->corr_func, status),
                oskar_mem_float(plane, status));
    }
}
예제 #7
0
void oskar_dftw(
        int normalise,
        int num_in,
        double wavenumber,
        const oskar_Mem* weights_in,
        const oskar_Mem* x_in,
        const oskar_Mem* y_in,
        const oskar_Mem* z_in,
        int offset_coord_out,
        int num_out,
        const oskar_Mem* x_out,
        const oskar_Mem* y_out,
        const oskar_Mem* z_out,
        const oskar_Mem* data,
        int offset_out,
        oskar_Mem* output,
        int* status)
{
    if (*status) return;
    const int location = oskar_mem_location(output);
    const int type = oskar_mem_precision(output);
    const int is_dbl = oskar_mem_is_double(output);
    const int is_3d = (z_in != NULL && z_out != NULL);
    const int is_data = (data != NULL);
    const int is_matrix = oskar_mem_is_matrix(output);
    if (!oskar_mem_is_complex(output) || !oskar_mem_is_complex(weights_in) ||
            oskar_mem_is_matrix(weights_in))
    {
        *status = OSKAR_ERR_BAD_DATA_TYPE;
        return;
    }
    if (oskar_mem_location(weights_in) != location ||
            oskar_mem_location(x_in) != location ||
            oskar_mem_location(y_in) != location ||
            oskar_mem_location(x_out) != location ||
            oskar_mem_location(y_out) != location)
    {
        *status = OSKAR_ERR_LOCATION_MISMATCH;
        return;
    }
    if (oskar_mem_precision(weights_in) != type ||
            oskar_mem_type(x_in) != type ||
            oskar_mem_type(y_in) != type ||
            oskar_mem_type(x_out) != type ||
            oskar_mem_type(y_out) != type)
    {
        *status = OSKAR_ERR_TYPE_MISMATCH;
        return;
    }
    if (is_data)
    {
        if (oskar_mem_location(data) != location)
        {
            *status = OSKAR_ERR_LOCATION_MISMATCH;
            return;
        }
        if (!oskar_mem_is_complex(data) ||
                oskar_mem_type(data) != oskar_mem_type(output) ||
                oskar_mem_precision(data) != type)
        {
            *status = OSKAR_ERR_TYPE_MISMATCH;
            return;
        }
    }
    if (is_3d)
    {
        if (oskar_mem_location(z_in) != location ||
                oskar_mem_location(z_out) != location)
        {
            *status = OSKAR_ERR_LOCATION_MISMATCH;
            return;
        }
        if (oskar_mem_type(z_in) != type || oskar_mem_type(z_out) != type)
        {
            *status = OSKAR_ERR_TYPE_MISMATCH;
            return;
        }
    }
    oskar_mem_ensure(output, (size_t) offset_out + num_out, status);
    if (*status) return;
    if (location == OSKAR_CPU)
    {
        if (is_data)
        {
            if (is_matrix)
            {
                if (is_3d)
                {
                    if (is_dbl)
                        dftw_m2m_3d_double(num_in, wavenumber,
                                oskar_mem_double2_const(weights_in, status),
                                oskar_mem_double_const(x_in, status),
                                oskar_mem_double_const(y_in, status),
                                oskar_mem_double_const(z_in, status),
                                offset_coord_out, num_out,
                                oskar_mem_double_const(x_out, status),
                                oskar_mem_double_const(y_out, status),
                                oskar_mem_double_const(z_out, status),
                                oskar_mem_double4c_const(data, status),
                                offset_out,
                                oskar_mem_double4c(output, status), 0);
                    else
                        dftw_m2m_3d_float(num_in, (float)wavenumber,
                                oskar_mem_float2_const(weights_in, status),
                                oskar_mem_float_const(x_in, status),
                                oskar_mem_float_const(y_in, status),
                                oskar_mem_float_const(z_in, status),
                                offset_coord_out, num_out,
                                oskar_mem_float_const(x_out, status),
                                oskar_mem_float_const(y_out, status),
                                oskar_mem_float_const(z_out, status),
                                oskar_mem_float4c_const(data, status),
                                offset_out,
                                oskar_mem_float4c(output, status), 0);
                }
                else
                {
                    if (is_dbl)
                        dftw_m2m_2d_double(num_in, wavenumber,
                                oskar_mem_double2_const(weights_in, status),
                                oskar_mem_double_const(x_in, status),
                                oskar_mem_double_const(y_in, status), 0,
                                offset_coord_out, num_out,
                                oskar_mem_double_const(x_out, status),
                                oskar_mem_double_const(y_out, status), 0,
                                oskar_mem_double4c_const(data, status),
                                offset_out,
                                oskar_mem_double4c(output, status), 0);
                    else
                        dftw_m2m_2d_float(num_in, (float)wavenumber,
                                oskar_mem_float2_const(weights_in, status),
                                oskar_mem_float_const(x_in, status),
                                oskar_mem_float_const(y_in, status), 0,
                                offset_coord_out, num_out,
                                oskar_mem_float_const(x_out, status),
                                oskar_mem_float_const(y_out, status), 0,
                                oskar_mem_float4c_const(data, status),
                                offset_out,
                                oskar_mem_float4c(output, status), 0);
                }
            }
            else
            {
                if (is_3d)
                {
                    if (is_dbl)
                        dftw_c2c_3d_double(num_in, wavenumber,
                                oskar_mem_double2_const(weights_in, status),
                                oskar_mem_double_const(x_in, status),
                                oskar_mem_double_const(y_in, status),
                                oskar_mem_double_const(z_in, status),
                                offset_coord_out, num_out,
                                oskar_mem_double_const(x_out, status),
                                oskar_mem_double_const(y_out, status),
                                oskar_mem_double_const(z_out, status),
                                oskar_mem_double2_const(data, status),
                                offset_out,
                                oskar_mem_double2(output, status), 0);
                    else
                        dftw_c2c_3d_float(num_in, (float)wavenumber,
                                oskar_mem_float2_const(weights_in, status),
                                oskar_mem_float_const(x_in, status),
                                oskar_mem_float_const(y_in, status),
                                oskar_mem_float_const(z_in, status),
                                offset_coord_out, num_out,
                                oskar_mem_float_const(x_out, status),
                                oskar_mem_float_const(y_out, status),
                                oskar_mem_float_const(z_out, status),
                                oskar_mem_float2_const(data, status),
                                offset_out,
                                oskar_mem_float2(output, status), 0);
                }
                else
                {
                    if (is_dbl)
                        dftw_c2c_2d_double(num_in, wavenumber,
                                oskar_mem_double2_const(weights_in, status),
                                oskar_mem_double_const(x_in, status),
                                oskar_mem_double_const(y_in, status), 0,
                                offset_coord_out, num_out,
                                oskar_mem_double_const(x_out, status),
                                oskar_mem_double_const(y_out, status), 0,
                                oskar_mem_double2_const(data, status),
                                offset_out,
                                oskar_mem_double2(output, status), 0);
                    else
                        dftw_c2c_2d_float(num_in, (float)wavenumber,
                                oskar_mem_float2_const(weights_in, status),
                                oskar_mem_float_const(x_in, status),
                                oskar_mem_float_const(y_in, status), 0,
                                offset_coord_out, num_out,
                                oskar_mem_float_const(x_out, status),
                                oskar_mem_float_const(y_out, status), 0,
                                oskar_mem_float2_const(data, status),
                                offset_out,
                                oskar_mem_float2(output, status), 0);
                }
            }
        }
        else
        {
            if (is_3d)
            {
                if (is_dbl)
                    dftw_o2c_3d_double(num_in, wavenumber,
                            oskar_mem_double2_const(weights_in, status),
                            oskar_mem_double_const(x_in, status),
                            oskar_mem_double_const(y_in, status),
                            oskar_mem_double_const(z_in, status),
                            offset_coord_out, num_out,
                            oskar_mem_double_const(x_out, status),
                            oskar_mem_double_const(y_out, status),
                            oskar_mem_double_const(z_out, status),
                            0, offset_out,
                            oskar_mem_double2(output, status), 0);
                else
                    dftw_o2c_3d_float(num_in, (float)wavenumber,
                            oskar_mem_float2_const(weights_in, status),
                            oskar_mem_float_const(x_in, status),
                            oskar_mem_float_const(y_in, status),
                            oskar_mem_float_const(z_in, status),
                            offset_coord_out, num_out,
                            oskar_mem_float_const(x_out, status),
                            oskar_mem_float_const(y_out, status),
                            oskar_mem_float_const(z_out, status),
                            0, offset_out,
                            oskar_mem_float2(output, status), 0);
            }
            else
            {
                if (is_dbl)
                    dftw_o2c_2d_double(num_in, wavenumber,
                            oskar_mem_double2_const(weights_in, status),
                            oskar_mem_double_const(x_in, status),
                            oskar_mem_double_const(y_in, status), 0,
                            offset_coord_out, num_out,
                            oskar_mem_double_const(x_out, status),
                            oskar_mem_double_const(y_out, status), 0,
                            0, offset_out,
                            oskar_mem_double2(output, status), 0);
                else
                    dftw_o2c_2d_float(num_in, (float)wavenumber,
                            oskar_mem_float2_const(weights_in, status),
                            oskar_mem_float_const(x_in, status),
                            oskar_mem_float_const(y_in, status), 0,
                            offset_coord_out, num_out,
                            oskar_mem_float_const(x_out, status),
                            oskar_mem_float_const(y_out, status), 0,
                            0, offset_out,
                            oskar_mem_float2(output, status), 0);
            }
        }
    }
    else
    {
        size_t local_size[] = {256, 1, 1}, global_size[] = {1, 1, 1};
        const void* np = 0;
        const char* k = 0;
        int max_in_chunk;
        float wavenumber_f = (float) wavenumber;

        /* Select the kernel. */
        switch (is_dbl * DBL | is_3d * D3 | is_data * DAT | is_matrix * MAT)
        {
        case D2 | FLT:             k = "dftw_o2c_2d_float";  break;
        case D2 | DBL:             k = "dftw_o2c_2d_double"; break;
        case D3 | FLT:             k = "dftw_o2c_3d_float";  break;
        case D3 | DBL:             k = "dftw_o2c_3d_double"; break;
        case D2 | FLT | DAT:       k = "dftw_c2c_2d_float";  break;
        case D2 | DBL | DAT:       k = "dftw_c2c_2d_double"; break;
        case D3 | FLT | DAT:       k = "dftw_c2c_3d_float";  break;
        case D3 | DBL | DAT:       k = "dftw_c2c_3d_double"; break;
        case D2 | FLT | DAT | MAT: k = "dftw_m2m_2d_float";  break;
        case D2 | DBL | DAT | MAT: k = "dftw_m2m_2d_double"; break;
        case D3 | FLT | DAT | MAT: k = "dftw_m2m_3d_float";  break;
        case D3 | DBL | DAT | MAT: k = "dftw_m2m_3d_double"; break;
        default:
            *status = OSKAR_ERR_FUNCTION_NOT_AVAILABLE;
            return;
        }
        if (oskar_device_is_nv(location))
            local_size[0] = (size_t) get_block_size(num_out);
        oskar_device_check_local_size(location, 0, local_size);
        global_size[0] = oskar_device_global_size(
                (size_t) num_out, local_size[0]);

        /* max_in_chunk must be multiple of 16. */
        max_in_chunk = is_3d ? (is_dbl ? 384 : 800) : (is_dbl ? 448 : 896);
        if (is_data && is_3d && !is_dbl) max_in_chunk = 768;
        const size_t element_size = is_dbl ? sizeof(double) : sizeof(float);
        const size_t local_mem_size = max_in_chunk * element_size;
        const size_t arg_size_local[] = {
                2 * local_mem_size, 2 * local_mem_size,
                (is_3d ? local_mem_size : 0)
        };

        /* Set kernel arguments. */
        const oskar_Arg args[] = {
                {INT_SZ, &num_in},
                {is_dbl ? DBL_SZ : FLT_SZ,
                        is_dbl ? (void*)&wavenumber : (void*)&wavenumber_f},
                {PTR_SZ, oskar_mem_buffer_const(weights_in)},
                {PTR_SZ, oskar_mem_buffer_const(x_in)},
                {PTR_SZ, oskar_mem_buffer_const(y_in)},
                {PTR_SZ, is_3d ? oskar_mem_buffer_const(z_in) : &np},
                {INT_SZ, &offset_coord_out},
                {INT_SZ, &num_out},
                {PTR_SZ, oskar_mem_buffer_const(x_out)},
                {PTR_SZ, oskar_mem_buffer_const(y_out)},
                {PTR_SZ, is_3d ? oskar_mem_buffer_const(z_out) : &np},
                {PTR_SZ, is_data ? oskar_mem_buffer_const(data) : &np},
                {INT_SZ, &offset_out},
                {PTR_SZ, oskar_mem_buffer(output)},
                {INT_SZ, &max_in_chunk}
        };
        oskar_device_launch_kernel(k, location, 1, local_size, global_size,
                sizeof(args) / sizeof(oskar_Arg), args,
                sizeof(arg_size_local) / sizeof(size_t), arg_size_local,
                status);
    }
    if (normalise)
        oskar_mem_scale_real(output, 1. / num_in, offset_out, num_out, status);
}
static void oskar_evaluate_station_beam_aperture_array_private(oskar_Mem* beam,
        const oskar_Station* s, int num_points, const oskar_Mem* x,
        const oskar_Mem* y, const oskar_Mem* z, double gast,
        double frequency_hz, oskar_StationWork* work, int time_index,
        int depth, int* status)
{
    double beam_x, beam_y, beam_z, wavenumber;
    oskar_Mem *weights, *weights_error, *theta, *phi, *array;
    int num_elements, is_3d;

    num_elements  = oskar_station_num_elements(s);
    is_3d         = oskar_station_array_is_3d(s);
    weights       = work->weights;
    weights_error = work->weights_error;
    theta         = work->theta_modified;
    phi           = work->phi_modified;
    array         = work->array_pattern;
    wavenumber    = 2.0 * M_PI * frequency_hz / 299792458.0;

    /* Check if safe to proceed. */
    if (*status) return;

    /* Compute direction cosines for the beam for this station. */
    oskar_evaluate_beam_horizon_direction(&beam_x, &beam_y, &beam_z, s,
            gast, status);

    /* Evaluate beam if there are no child stations. */
    if (!oskar_station_has_child(s))
    {
        /* First optimisation: A single element model type, and either a common
         * orientation for all elements within the station, or isotropic
         * elements. */
        /* Array pattern and element pattern are separable. */
        if (oskar_station_num_element_types(s) == 1 &&
                (oskar_station_common_element_orientation(s) ||
                        oskar_element_type(oskar_station_element_const(s, 0))
                        == OSKAR_ELEMENT_TYPE_ISOTROPIC) )
        {
            /* (Always) evaluate element pattern into the output beam array. */
            oskar_element_evaluate(oskar_station_element_const(s, 0), beam,
                    oskar_station_element_x_alpha_rad(s, 0) + M_PI/2.0, /* FIXME Will change: This matches the old convention. */
                    oskar_station_element_y_alpha_rad(s, 0),
                    num_points, x, y, z, frequency_hz, theta, phi, status);

            /* Check if array pattern is enabled. */
            if (oskar_station_enable_array_pattern(s))
            {
                /* Generate beamforming weights and evaluate array pattern. */
                oskar_evaluate_element_weights(weights, weights_error,
                        wavenumber, s, beam_x, beam_y, beam_z,
                        time_index, status);
                oskar_dftw(num_elements, wavenumber,
                        oskar_station_element_true_x_enu_metres_const(s),
                        oskar_station_element_true_y_enu_metres_const(s),
                        oskar_station_element_true_z_enu_metres_const(s),
                        weights, num_points, x, y, (is_3d ? z : 0), 0, array,
                        status);

                /* Normalise array response if required. */
                if (oskar_station_normalise_array_pattern(s))
                    oskar_mem_scale_real(array, 1.0 / num_elements, status);

                /* Element-wise multiply to join array and element pattern. */
                oskar_mem_multiply(beam, beam, array, num_points, status);
            }
        }

#if 0
        /* Second optimisation: Common orientation for all elements within the
         * station, but more than one element type. */
        else if (oskar_station_common_element_orientation(s))
        {
            /* Must evaluate array pattern, so check that this is enabled. */
            if (!oskar_station_enable_array_pattern(s))
            {
                *status = OSKAR_ERR_SETTINGS_TELESCOPE;
                return;
            }

            /* Call a DFT using indexed input. */
            /* TODO Not yet implemented. */
            *status = OSKAR_ERR_FUNCTION_NOT_AVAILABLE;
        }
#endif

        /* No optimisation: No common element orientation. */
        /* Can't separate array and element evaluation. */
        else
        {
            int i, num_element_types;
            oskar_Mem *element_block = 0, *element = 0;
            const int* element_type_array = 0;

            /* Must evaluate array pattern, so check that this is enabled. */
            if (!oskar_station_enable_array_pattern(s))
            {
                *status = OSKAR_ERR_SETTINGS_TELESCOPE;
                return;
            }

            /* Get sized element pattern block (at depth 0). */
            element_block = oskar_station_work_beam(work, beam,
                    num_elements * num_points, 0, status);

            /* Create alias into element block. */
            element = oskar_mem_create_alias(element_block, 0, 0, status);

            /* Loop over elements and evaluate response for each. */
            element_type_array = oskar_station_element_types_cpu_const(s);
            num_element_types = oskar_station_num_element_types(s);
            for (i = 0; i < num_elements; ++i)
            {
                int element_type_idx;
                element_type_idx = element_type_array[i];
                if (element_type_idx >= num_element_types)
                {
                    *status = OSKAR_ERR_OUT_OF_RANGE;
                    break;
                }
                oskar_mem_set_alias(element, element_block, i * num_points,
                        num_points, status);
                oskar_element_evaluate(
                        oskar_station_element_const(s, element_type_idx),
                        element,
                        oskar_station_element_x_alpha_rad(s, i) + M_PI/2.0, /* FIXME Will change: This matches the old convention. */
                        oskar_station_element_y_alpha_rad(s, i),
                        num_points, x, y, z, frequency_hz, theta, phi, status);
            }

            /* Generate beamforming weights. */
            oskar_evaluate_element_weights(weights, weights_error,
                    wavenumber, s, beam_x, beam_y, beam_z,
                    time_index, status);

            /* Use DFT to evaluate array response. */
            oskar_dftw(num_elements, wavenumber,
                    oskar_station_element_true_x_enu_metres_const(s),
                    oskar_station_element_true_y_enu_metres_const(s),
                    oskar_station_element_true_z_enu_metres_const(s),
                    weights, num_points, x, y, (is_3d ? z : 0),
                    element_block, beam, status);

            /* Free element alias. */
            oskar_mem_free(element, status);

            /* Normalise array response if required. */
            if (oskar_station_normalise_array_pattern(s))
                oskar_mem_scale_real(beam, 1.0 / num_elements, status);
        }

        /* Blank (set to zero) points below the horizon. */
        oskar_blank_below_horizon(num_points, z, beam, status);
    }

    /* If there are child stations, must first evaluate the beam for each. */
    else
    {
        int i;
        oskar_Mem* signal;

        /* Must evaluate array pattern, so check that this is enabled. */
        if (!oskar_station_enable_array_pattern(s))
        {
            *status = OSKAR_ERR_SETTINGS_TELESCOPE;
            return;
        }

        /* Get sized work array for this depth, with the correct type. */
        signal = oskar_station_work_beam(work, beam, num_elements * num_points,
                depth, status);

        /* Check if child stations are identical. */
        if (oskar_station_identical_children(s))
        {
            /* Set up the output buffer for the first station. */
            oskar_Mem* output0;
            output0 = oskar_mem_create_alias(signal, 0, num_points, status);

            /* Recursive call. */
            oskar_evaluate_station_beam_aperture_array_private(output0,
                    oskar_station_child_const(s, 0), num_points,
                    x, y, z, gast, frequency_hz, work, time_index,
                    depth + 1, status);

            /* Copy beam for child station 0 into memory for other stations. */
            for (i = 1; i < num_elements; ++i)
            {
                oskar_mem_copy_contents(signal, output0, i * num_points, 0,
                        oskar_mem_length(output0), status);
            }
            oskar_mem_free(output0, status);
        }
        else
        {
            /* Loop over child stations. */
            for (i = 0; i < num_elements; ++i)
            {
                /* Set up the output buffer for this station. */
                oskar_Mem* output;
                output = oskar_mem_create_alias(signal, i * num_points,
                        num_points, status);

                /* Recursive call. */
                oskar_evaluate_station_beam_aperture_array_private(output,
                        oskar_station_child_const(s, i), num_points,
                        x, y, z, gast, frequency_hz, work, time_index,
                        depth + 1, status);
                oskar_mem_free(output, status);
            }
        }

        /* Generate beamforming weights and form beam from child stations. */
        oskar_evaluate_element_weights(weights, weights_error, wavenumber,
                s, beam_x, beam_y, beam_z, time_index, status);
        oskar_dftw(num_elements, wavenumber,
                oskar_station_element_true_x_enu_metres_const(s),
                oskar_station_element_true_y_enu_metres_const(s),
                oskar_station_element_true_z_enu_metres_const(s),
                weights, num_points, x, y, (is_3d ? z : 0), signal, beam,
                status);

        /* Normalise array response if required. */
        if (oskar_station_normalise_array_pattern(s))
            oskar_mem_scale_real(beam, 1.0 / num_elements, status);
    }
}