void oskar_station_override_element_time_variable_phases(oskar_Station* s,
        double phase_std, int* status)
{
    int i;

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

    /* Check if there are child stations. */
    if (oskar_station_has_child(s))
    {
        /* Recursive call to find the last level (i.e. the element data). */
        for (i = 0; i < s->num_elements; ++i)
        {
            oskar_station_override_element_time_variable_phases(
                    oskar_station_child(s, i), phase_std, status);
        }
    }
    else
    {
        /* Override element data at last level. */
        oskar_mem_set_value_real(s->element_phase_error_rad,
                phase_std, 0, 0, status);
    }
}
void oskar_evaluate_station_beam_aperture_array(oskar_Mem* beam,
        const oskar_Station* station, 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* status)
{
    int start;

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

    /* Evaluate beam immediately, without chunking, if there are no
     * child stations. */
    if (!oskar_station_has_child(station))
    {
        oskar_evaluate_station_beam_aperture_array_private(beam, station,
                num_points, x, y, z, gast, frequency_hz, work,
                time_index, 0, status);
    }
    else
    {
        oskar_Mem *c_beam, *c_x, *c_y, *c_z;
        c_beam = oskar_mem_create_alias(0, 0, 0, status);
        c_x = oskar_mem_create_alias(0, 0, 0, status);
        c_y = oskar_mem_create_alias(0, 0, 0, status);
        c_z = oskar_mem_create_alias(0, 0, 0, status);

        /* Split up list of input points into manageable chunks. */
        for (start = 0; start < num_points; start += MAX_CHUNK_SIZE)
        {
            int chunk_size;

            /* Get size of current chunk. */
            chunk_size = num_points - start;
            if (chunk_size > MAX_CHUNK_SIZE) chunk_size = MAX_CHUNK_SIZE;

            /* Get pointers to start of chunk input data. */
            oskar_mem_set_alias(c_beam, beam, start, chunk_size, status);
            oskar_mem_set_alias(c_x, x, start, chunk_size, status);
            oskar_mem_set_alias(c_y, y, start, chunk_size, status);
            oskar_mem_set_alias(c_z, z, start, chunk_size, status);

            /* Start recursive call at depth 1 (depth 0 is element level). */
            oskar_evaluate_station_beam_aperture_array_private(c_beam, station,
                    chunk_size, c_x, c_y, c_z, gast, frequency_hz, work,
                    time_index, 1, status);
        }

        /* Release handles for chunk memory. */
        oskar_mem_free(c_beam, status);
        oskar_mem_free(c_x, status);
        oskar_mem_free(c_y, status);
        oskar_mem_free(c_z, status);
    }
}
static void oskar_telescope_set_station_type_p(oskar_Station* station, int type)
{
    oskar_station_set_station_type(station, type);
    if (oskar_station_has_child(station))
    {
        int i, num_elements;
        num_elements = oskar_station_num_elements(station);
        for (i = 0; i < num_elements; ++i)
        {
            oskar_telescope_set_station_type_p(
                    oskar_station_child(station, i), type);
        }
    }
}
void oskar_station_override_element_feed_angle(oskar_Station* s,
        unsigned int seed, int x_pol, double alpha_error_rad,
        double beta_error_rad, double gamma_error_rad, int* status)
{
    int i;

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

    /* Check if there are child stations. */
    if (oskar_station_has_child(s))
    {
        /* Recursive call to find the last level (i.e. the element data). */
        for (i = 0; i < s->num_elements; ++i)
        {
            oskar_station_override_element_feed_angle(
                    oskar_station_child(s, i), seed, x_pol,
                    alpha_error_rad, beta_error_rad, gamma_error_rad, status);
        }
    }
    else
    {
        /* Override element data at last level. */
        int id;
        double *a, *b, *c, r[4];
        oskar_Mem *alpha, *beta, *gamma;

        /* Get pointer to the X or Y element orientation data. */
        alpha = x_pol ? s->element_x_alpha_cpu : s->element_y_alpha_cpu;
        beta  = x_pol ? s->element_x_beta_cpu : s->element_y_beta_cpu;
        gamma = x_pol ? s->element_x_gamma_cpu : s->element_y_gamma_cpu;
        a = oskar_mem_double(alpha, status);
        b = oskar_mem_double(beta, status);
        c = oskar_mem_double(gamma, status);
        id = oskar_station_unique_id(s);
        for (i = 0; i < s->num_elements; ++i)
        {
            /* Generate random numbers from Gaussian distribution. */
            oskar_random_gaussian4(seed, i, id, 0, 0, r);
            r[0] *= alpha_error_rad;
            r[1] *= beta_error_rad;
            r[2] *= gamma_error_rad;

            /* Set the new angle. */
            a[i] += r[0];
            b[i] += r[1];
            c[i] += r[2];
        }
    }
}
static void oskar_telescope_set_gaussian_station_beam_p(oskar_Station* station,
        double fwhm_rad, double ref_freq_hz)
{
    oskar_station_set_gaussian_beam_values(station, fwhm_rad, ref_freq_hz);
    if (oskar_station_has_child(station))
    {
        int i, num_elements;
        num_elements = oskar_station_num_elements(station);
        for (i = 0; i < num_elements; ++i)
        {
            oskar_telescope_set_gaussian_station_beam_p(
                    oskar_station_child(station, i), fwhm_rad, ref_freq_hz);
        }
    }
}
static void max_station_size_and_depth(const oskar_Station* s,
        int* max_elements, int* max_depth, int depth)
{
    int i, num_elements;
    num_elements = oskar_station_num_elements(s);
    *max_elements = num_elements > *max_elements ? num_elements : *max_elements;
    *max_depth = depth > *max_depth ? depth : *max_depth;
    if (oskar_station_has_child(s))
    {
        for (i = 0; i < num_elements; ++i)
        {
            max_station_size_and_depth(oskar_station_child_const(s, i),
                    max_elements, max_depth, depth + 1);
        }
    }
}
static void oskar_telescope_set_station_phase_centre(oskar_Station* station,
        int coord_type, double ra_rad, double dec_rad)
{
    oskar_station_set_phase_centre(station, coord_type, ra_rad, dec_rad);
    if (oskar_station_has_child(station))
    {
        int i, num_elements;
        num_elements = oskar_station_num_elements(station);
        for (i = 0; i < num_elements; ++i)
        {
            oskar_telescope_set_station_phase_centre(
                    oskar_station_child(station, i),
                    coord_type, ra_rad, dec_rad);
        }
    }
}
void oskar_station_set_polar_motion(oskar_Station* model,
                                    double pm_x_rad, double pm_y_rad)
{
    int i;
    model->pm_x_rad = pm_x_rad;
    model->pm_y_rad = pm_y_rad;

    /* Set recursively for all child stations. */
    if (oskar_station_has_child(model))
    {
        for (i = 0; i < model->num_elements; ++i)
        {
            oskar_station_set_polar_motion(model->child[i], pm_x_rad, pm_y_rad);
        }
    }
}
static void set_child_station_locations(oskar_Station* s,
        const oskar_Station* parent)
{
    if (parent)
    {
        oskar_station_set_position(s,
                oskar_station_lon_rad(parent),
                oskar_station_lat_rad(parent),
                oskar_station_alt_metres(parent));
    }

    /* Recursively set data for child stations. */
    if (oskar_station_has_child(s))
    {
        int i, num_elements;
        num_elements = oskar_station_num_elements(s);
        for (i = 0; i < num_elements; ++i)
            set_child_station_locations(oskar_station_child(s, i), s);
    }
}
oskar_Station* oskar_station_create_copy(const oskar_Station* src,
        int location, int* status)
{
    int i = 0;
    oskar_Station* model = 0;

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

    /* Create the new model. */
    model = oskar_station_create(oskar_station_precision(src), location,
            oskar_station_num_elements(src), status);

    /* Set meta-data. */
    model->unique_id = src->unique_id;
    model->precision = src->precision;
    model->mem_location = location;

    /* Copy common station parameters. */
    model->station_type = src->station_type;
    model->normalise_final_beam = src->normalise_final_beam;
    model->lon_rad = src->lon_rad;
    model->lat_rad = src->lat_rad;
    model->alt_metres = src->alt_metres;
    model->pm_x_rad = src->pm_x_rad;
    model->pm_y_rad = src->pm_y_rad;
    model->beam_lon_rad = src->beam_lon_rad;
    model->beam_lat_rad = src->beam_lat_rad;
    model->beam_coord_type = src->beam_coord_type;
    oskar_mem_copy(model->noise_freq_hz, src->noise_freq_hz, status);
    oskar_mem_copy(model->noise_rms_jy, src->noise_rms_jy, status);

    /* Copy aperture array data, except num_element_types (done later). */
    model->identical_children = src->identical_children;
    model->num_elements = src->num_elements;
    model->normalise_array_pattern = src->normalise_array_pattern;
    model->enable_array_pattern = src->enable_array_pattern;
    model->common_element_orientation = src->common_element_orientation;
    model->array_is_3d = src->array_is_3d;
    model->apply_element_errors = src->apply_element_errors;
    model->apply_element_weight = src->apply_element_weight;
    model->seed_time_variable_errors = src->seed_time_variable_errors;
    model->num_permitted_beams = src->num_permitted_beams;

    /* Copy Gaussian station beam data. */
    model->gaussian_beam_fwhm_rad = src->gaussian_beam_fwhm_rad;
    model->gaussian_beam_reference_freq_hz = src->gaussian_beam_reference_freq_hz;

    /* Copy memory blocks. */
    oskar_mem_copy(model->element_true_x_enu_metres,
            src->element_true_x_enu_metres, status);
    oskar_mem_copy(model->element_true_y_enu_metres,
            src->element_true_y_enu_metres, status);
    oskar_mem_copy(model->element_true_z_enu_metres,
            src->element_true_z_enu_metres, status);
    oskar_mem_copy(model->element_measured_x_enu_metres,
            src->element_measured_x_enu_metres, status);
    oskar_mem_copy(model->element_measured_y_enu_metres,
            src->element_measured_y_enu_metres, status);
    oskar_mem_copy(model->element_measured_z_enu_metres,
            src->element_measured_z_enu_metres, status);
    oskar_mem_copy(model->element_weight, src->element_weight, status);
    oskar_mem_copy(model->element_gain, src->element_gain, status);
    oskar_mem_copy(model->element_gain_error, src->element_gain_error, status);
    oskar_mem_copy(model->element_phase_offset_rad,
            src->element_phase_offset_rad, status);
    oskar_mem_copy(model->element_phase_error_rad,
            src->element_phase_error_rad, status);
    oskar_mem_copy(model->element_x_alpha_cpu,
            src->element_x_alpha_cpu, status);
    oskar_mem_copy(model->element_x_beta_cpu,
            src->element_x_beta_cpu, status);
    oskar_mem_copy(model->element_x_gamma_cpu,
            src->element_x_gamma_cpu, status);
    oskar_mem_copy(model->element_y_alpha_cpu,
            src->element_y_alpha_cpu, status);
    oskar_mem_copy(model->element_y_beta_cpu,
            src->element_y_beta_cpu, status);
    oskar_mem_copy(model->element_y_gamma_cpu,
            src->element_y_gamma_cpu, status);
    oskar_mem_copy(model->element_types, src->element_types, status);
    oskar_mem_copy(model->element_types_cpu, src->element_types_cpu, status);
    oskar_mem_copy(model->element_mount_types_cpu, src->element_mount_types_cpu, status);
    oskar_mem_copy(model->permitted_beam_az_rad, src->permitted_beam_az_rad, status);
    oskar_mem_copy(model->permitted_beam_el_rad, src->permitted_beam_el_rad, status);

    /* Copy element models, if set. */
    if (oskar_station_has_element(src))
    {
        /* Ensure enough space for element model data. */
        oskar_station_resize_element_types(model, src->num_element_types,
                status);

        /* Copy the element model data. */
        for (i = 0; i < src->num_element_types; ++i)
        {
            oskar_element_copy(model->element[i], src->element[i], status);
        }
    }

    /* Recursively copy child stations. */
    if (oskar_station_has_child(src))
    {
        model->child = malloc(src->num_elements * sizeof(oskar_Station*));
        if (!model->child)
        {
            *status = OSKAR_ERR_MEMORY_ALLOC_FAILURE;
            return model;
        }

        for (i = 0; i < src->num_elements; ++i)
        {
            model->child[i] = oskar_station_create_copy(
                    oskar_station_child_const(src, i), location, status);
        }
    }

    return model;
}
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);
    }
}