/*******************************************************************************
* This file is part of SWIFT.
*
* The functions in this file are based on code from the HEALPix
* 3.80 library (see http://healpix.sourceforge.net):
*
* Copyright (C) 1997-2013 Krzysztof M. Gorski, Eric Hivon,
* Benjamin D. Wandelt, Anthony J. Banday,
* Matthias Bartelmann, Hans K. Eriksen,
* Frode K. Hansen, Martin Reinecke
*
* Translated and modified for SWIFT by John Helly:
*
* Copyright (c) 2021 John Helly (j.c.helly@durham.ac.uk)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*
******************************************************************************/
#include "lightcone/healpix_util.h"
#include
#include
#include
/**
* @brief Integer modulus function, valid for b > 0 only
*
* Note that result is not the same as a % b for negative
* values of a. If b > 0 then result is in range 0 to b-1
* inclusive.
*
* @param a the first integer
* @param a the second integer
*
*/
static int mod(int a, int b) {
int r = a % b;
return r < 0 ? r + b : r;
}
/**
* @brief Given a normalized z coordinate, return the ring
* number in range 1 to 4*nside-1.
*
* @param nside HEALPix resolution parameter
* @param z the z coordinate to check
*
*/
static int ring_num(int nside, double z) {
/* Equatorial regime */
int iring = round(nside * (2.0 - 1.5 * z));
/* North cap */
if (z > 2. / 3.) {
iring = round(nside * sqrt(3.0 * (1.0 - z)));
if (iring == 0) iring = 1;
}
/* South cap */
if (z < -2. / 3.) {
iring = round(nside * sqrt(3.0 * (1.0 + z)));
if (iring == 0) iring = 1;
iring = 4 * nside - iring;
}
return iring;
}
/**
* @brief Return information about the specified HEALPix ring
*
* @param nside HEALPix resolution parameter
* @param ring the ring index in range 1 to 4*Nside-1
* @param npr returns the number of pixels in the ring
* @param kshift returns the shift of this ring
* @param npnorth returns total number of pixels in this ring
* all rings to the north of this one
*
*/
static void pixels_per_ring(int nside, int ring, int *npr, int *kshift,
long long *npnorth) {
/* number of pixels in current ring */
*npr = nside;
if (ring < *npr) *npr = ring;
if (4 * nside - ring < *npr) *npr = 4 * nside - ring;
*npr *= 4;
/* Shift */
*kshift = (ring + 1) % 2; /* 1 for even, 0 for odd */
if (nside == 1) *kshift = 1 - *kshift; /* except for Nside=1 */
if (*npr < 4 * nside) *kshift = 1; /* 1 on polar cap */
/* Number of pixels in current ring and above */
if (ring <= nside) {
/* in North cap */
*npnorth = ring * (ring + 1ll) * 2ll;
} else if (ring <= 3 * nside) {
/* in Equatorial region */
long long ncap = nside * (nside + 1ll) * 2ll;
long long ir = ring - nside;
*npnorth = ncap + 4ll * nside * ir;
} else {
/* in South cap */
long long npix = (12ll * nside) * nside;
long long ir = 4ll * nside - ring - 1; /* count ring from south */
*npnorth = npix - ir * (ir + 1ll) * 2ll;
}
}
/**
* @brief Compute the z coordinate of a HEALPix ring
*
* @param nside HEALPix resolution parameter
* @param ir the ring index in range 1 to 4*Nside-1
*
*/
static double ring2z(int nside, int ir) {
double z;
double fn = (double)nside;
if (ir < nside) {
/* north polar cap */
double tmp = (double)ir;
z = 1.0 - (tmp * tmp) / (3.0 * fn * fn);
} else if (ir < 3 * nside) {
/* tropical band */
z = ((double)(2 * nside - ir)) * 2.0 / (3.0 * fn);
} else {
/* south polar cap */
double tmp = (double)(4 * nside - ir);
z = -1.0 + (tmp * tmp) / (3.0 * fn * fn);
}
return z;
}
/**
* @brief Find pixels with centres within specified radius of
* the given vector
*
* Based on query_disc() from src/f90/mod/pixel_routines.F90.
* Assumes RING indexing and does not support inclusive mode
* (i.e. only returns pixels with centres within radius)
*
* If nr_ranges and range are both not NULL, returns a newly
* allocated array of struct pixel_range with the ranges of
* pixels which overlap the disc.
*
* @param nside HEALPix resolution parameter
* @param vec vector specifying the disc centre
* @param radius the radius to search
* @param pix_min returns minimum pixel index in the disc
* @param pix_max returns maximum pixel index in the disc
* @param nr_ranges returns the size of the range array
* @param range returns a new array of struct pixel_range
*
*/
void healpix_query_disc_range(int nside, double vec[3], double radius,
pixel_index_t *pix_min, pixel_index_t *pix_max,
int *nr_ranges, struct pixel_range **range) {
/* Get normalized disc centre vector */
double norm = sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]);
double x0 = vec[0] / norm;
double y0 = vec[1] / norm;
double z0 = vec[2] / norm;
/* coordinate z of highest and lowest points in the disc */
double rlat0 = asin(z0); /* latitude in RAD of the center */
double rlat1 = rlat0 + radius;
double rlat2 = rlat0 - radius;
double zmin, zmax;
if (rlat1 >= 0.5 * M_PI) {
zmax = 1.0;
} else {
zmax = sin(rlat1);
}
if (rlat2 <= -0.5 * M_PI) {
zmin = -1.0;
} else {
zmin = sin(rlat2);
}
/* Find which rings overlap the disc */
int irmin = ring_num(nside, zmax);
if (irmin - 1 > 1) {
irmin = irmin - 1;
} else {
irmin = 1;
}
int irmax = ring_num(nside, zmin);
if (irmax + 1 < 4 * nside - 1) {
irmax = irmax + 1;
} else {
irmax = 4 * nside - 1;
}
/* Get phi at disc centre */
double phi0 = 0.0;
if ((x0 != 0) || (y0 != 0)) phi0 = atan2(y0, x0);
/* Allocate output array:
need to allow for worst case where all rings cross the periodic
boundary and therefore contribute two disjoint ranges of pixels */
int nout_max = 2 * (irmax - irmin + 1);
if (nout_max < 1) nout_max = 1;
if (nr_ranges && range) {
*range =
(struct pixel_range *)malloc(nout_max * sizeof(struct pixel_range));
}
/* Will return min and max pixel indexes in the disk */
long long npix = (12ll * nside) * nside;
long long pix_min_ll = npix;
long long pix_max_ll = -1;
/* Now have min/max ring index (in range 1 to 4*nside-1) */
/* Loop over rings which overlap the disc */
if (nr_ranges && range) *nr_ranges = 0;
for (int iring = irmin; iring <= irmax; iring += 1) {
/* Find z coordinate of this ring */
double z = ring2z(nside, iring);
/* Find range in phi which overlaps the disc in this ring:
taken from discphirange_at_z() in pix_tools.F90 */
double cosang = cos(radius);
double a = x0 * x0 + y0 * y0;
double dphi = -1000.0; /* Indicates outside disc */
double b = cosang - z * z0;
if (a == 0.0) { /* Poles */
if (b <= 0.0) dphi = M_PI;
} else {
double c = fmax(1.0 - z * z, 1.0e-12);
double cosdphi = b / sqrt(a * c);
if (cosdphi < -1.0)
dphi = M_PI; /* all the pixels at this elevation are in the disc */
if (fabs(cosdphi) <= 1.0) dphi = acos(cosdphi); /* in [0,Pi] */
}
/* Look up number of pixels in this ring */
int npr, kshift;
long long npnorth;
pixels_per_ring(nside, iring, &npr, &kshift, &npnorth);
/* For each ring, store the range of pixels which overlaps the disc.
If the disc overlaps the periodic boundary at phi=2pi we need to split
the range into two pieces. */
int my_low = -1;
int my_hi = -1;
if (dphi > M_PI) {
/* Full ring */
my_low = 0;
my_hi = npr - 1;
} else if (dphi >= 0.0) {
/* Partial ring */
double shift = kshift * 0.5;
int iphi_low = ceil(npr * (phi0 - dphi) / (2 * M_PI) - shift);
int iphi_hi = floor(npr * (phi0 + dphi) / (2 * M_PI) - shift);
if (iphi_hi >= iphi_low) {
my_low = mod(iphi_low, npr);
my_hi = mod(iphi_hi, npr);
}
}
if (my_low >= 0) {
long long first;
long long last;
if (my_hi >= my_low) {
/* Not crossing periodic boundary, so we can return a single range */
first = (npnorth - npr) + my_low;
last = (npnorth - npr) + my_hi;
if (first < pix_min_ll) pix_min_ll = first;
if (last > pix_max_ll) pix_max_ll = last;
if (nr_ranges && range) {
(*range)[*nr_ranges].first = first;
(*range)[*nr_ranges].last = last;
*nr_ranges += 1;
}
} else {
/* Range overlaps periodic boundary, so will be split in two */
/* Start of ring to my_hi */
first = (npnorth - npr) + 0;
last = (npnorth - npr) + my_hi;
if (first < pix_min_ll) pix_min_ll = first;
if (nr_ranges && range) {
(*range)[*nr_ranges].first = first;
(*range)[*nr_ranges].last = last;
*nr_ranges += 1;
}
/* my_low to end of ring */
first = (npnorth - npr) + my_low;
last = (npnorth - npr) + (npr - 1);
if (last > pix_max_ll) pix_max_ll = last;
if (nr_ranges && range) {
(*range)[*nr_ranges].first = first;
(*range)[*nr_ranges].last = last;
*nr_ranges += 1;
}
}
}
/* Next ring */
}
/* Return min and max pixel indexes */
*pix_min = (pixel_index_t)pix_min_ll;
*pix_max = (pixel_index_t)pix_max_ll;
}
/**
* @brief Make a 3D vector given z and phi coordinates
*
* @param v returns the new vector
* @param z normalized coordinate in the z axis
* @param phi angular coordinate
*
*/
static void set_z_phi(double *v, double z, double phi) {
double sintheta = sqrt((1.0 - z) * (1.0 + z));
v[0] = sintheta * cos(phi);
v[1] = sintheta * sin(phi);
v[2] = z;
}
/**
* @brief Return the maximum radius of any pixel for a given nside.
*
* Based on Healpix_base::max_pixrad() from the C++ library.
*
* @param nside HEALPix resolution parameter
*
*/
double healpix_max_pixrad(int nside) {
double va[3];
set_z_phi(va, 2. / 3., M_PI / (4 * nside));
double t1 = 1. - 1. / nside;
t1 *= t1;
double vb[3];
set_z_phi(vb, 1. - t1 / 3., 0.);
double dotprod = va[0] * vb[0] + va[1] * vb[1] + va[2] * vb[2];
double crossprod[3];
crossprod[0] = va[1] * vb[2] - va[2] * vb[1];
crossprod[1] = va[2] * vb[0] - va[0] * vb[2];
crossprod[2] = va[0] * vb[1] - va[1] * vb[0];
double length =
sqrt(crossprod[0] * crossprod[0] + crossprod[1] * crossprod[1] +
crossprod[2] * crossprod[2]);
return atan2(length, dotprod);
}