Skip to content

Commit

Permalink
First pass at interpolation
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielaBreitman committed May 8, 2024
1 parent 3461cd3 commit 4372041
Showing 1 changed file with 77 additions and 6 deletions.
83 changes: 77 additions & 6 deletions src/powerbox/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import numpy as np
import warnings
from scipy.interpolate import RegularGridInterpolator
from scipy.special import gamma

from . import dft
Expand Down Expand Up @@ -175,9 +176,72 @@ def _get_binweights(coords, weights, bins, average=True, bin_ave=True, log_bins=
return indx, bins, sumweights


def _spherical2cartesian(r, phi_n):
"""Convert spherical coordinates to Cartesian coordinates."""
phi_n = np.concatenate(

Check warning on line 181 in src/powerbox/tools.py

View check run for this annotation

Codecov / codecov/patch

src/powerbox/tools.py#L181

Added line #L181 was not covered by tests
[np.array([2 * np.pi] * phi_n.shape[1])[np.newaxis, ...], phi_n], axis=0
)
sines = np.sin(phi_n)
sines[0, :] = 1
cum_sines = np.cumprod(sines, axis=0)
cosines = np.roll(np.cos(phi_n), -1, axis=0)
return cum_sines * cosines * r

Check warning on line 188 in src/powerbox/tools.py

View check run for this annotation

Codecov / codecov/patch

src/powerbox/tools.py#L184-L188

Added lines #L184 - L188 were not covered by tests


def _field_average_interpolate(coords, field, bins, weights, angular_resolution=0.1):
# Grid is regular + can be ordered only in Cartesian coords.
fnc = RegularGridInterpolator(

Check warning on line 193 in src/powerbox/tools.py

View check run for this annotation

Codecov / codecov/patch

src/powerbox/tools.py#L193

Added line #L193 was not covered by tests
coords,
field * weights, # Complex data is accepted.
bounds_error=False,
fill_value=None,
) # To extrapolate at the edges if needed.
# Evaluate it on points in angular coords that we then convert to Cartesian.
# Number of angular bins for each radius absk on which to calculate the interpolated power when doing the averaging
# Larger wavemodes / radii will have more samples in theta
# "bins" is always 1D
num_angular_bins = np.array(

Check warning on line 203 in src/powerbox/tools.py

View check run for this annotation

Codecov / codecov/patch

src/powerbox/tools.py#L203

Added line #L203 was not covered by tests
np.round(2 * np.pi * bins / angular_resolution), dtype=int
)
phi_1 = [np.linspace(0, 2 * np.pi, n) for n in num_angular_bins]

Check warning on line 206 in src/powerbox/tools.py

View check run for this annotation

Codecov / codecov/patch

src/powerbox/tools.py#L206

Added line #L206 was not covered by tests
# Angular resolution is same for all dims
phi_n = np.concatenate(

Check warning on line 208 in src/powerbox/tools.py

View check run for this annotation

Codecov / codecov/patch

src/powerbox/tools.py#L208

Added line #L208 was not covered by tests
[
np.array(
np.meshgrid(*([phi_1[i]] * (coords.shape[0] - 1)), sparse=False)
).reshape(
(coords.shape[0] - 1, num_angular_bins[i] ** (coords.shape[0] - 1))
)
for i in range(len(bins))
],
axis=-1,
)
r_n = np.concatenate(

Check warning on line 219 in src/powerbox/tools.py

View check run for this annotation

Codecov / codecov/patch

src/powerbox/tools.py#L219

Added line #L219 was not covered by tests
[
[r] * (num_angular_bins[i] ** (coords.shape[0] - 1))
for i, r in enumerate(bins)
]
)
sample_coords = _spherical2cartesian(r_n, phi_n)

Check warning on line 225 in src/powerbox/tools.py

View check run for this annotation

Codecov / codecov/patch

src/powerbox/tools.py#L225

Added line #L225 was not covered by tests
# To exclude parts of spherical shells outside of the cube corners (too far away for interpolation)
coords_outside_box = np.any(

Check warning on line 227 in src/powerbox/tools.py

View check run for this annotation

Codecov / codecov/patch

src/powerbox/tools.py#L227

Added line #L227 was not covered by tests
sample_coords >= coords.max(), axis=0
) # since coords is a symmetric cube centered at 0.
interped_field = fnc(sample_coords[:, ~coords_outside_box].T)
r_n = r_n[~coords_outside_box]

Check warning on line 231 in src/powerbox/tools.py

View check run for this annotation

Codecov / codecov/patch

src/powerbox/tools.py#L230-L231

Added lines #L230 - L231 were not covered by tests
# Average over the spherical shells for each radius / bin value
avged_field = np.array([np.mean(interped_field[r_n == b]) for b in bins])
sumweights = np.array([sum(r_n == b) for b in bins])
return avged_field, sumweights

Check warning on line 235 in src/powerbox/tools.py

View check run for this annotation

Codecov / codecov/patch

src/powerbox/tools.py#L233-L235

Added lines #L233 - L235 were not covered by tests


def _field_average(indx, field, weights, sumweights):
if not np.isscalar(weights) and field.shape != weights.shape:
raise ValueError("the field and weights must have the same shape!")
raise ValueError(

Check warning on line 240 in src/powerbox/tools.py

View check run for this annotation

Codecov / codecov/patch

src/powerbox/tools.py#L240

Added line #L240 was not covered by tests
"the field and weights must have the same shape!",
field.shape,
weights.shape,
)

field = field * weights # Leave like this because field is mutable

Expand Down Expand Up @@ -239,6 +303,7 @@ def angular_average_nd(
bin_ave=True,
get_variance=False,
log_bins=False,
interpolation_method=None,
):
"""
Average the first n dimensions of a given field within radial bins.
Expand Down Expand Up @@ -350,17 +415,21 @@ def angular_average_nd(
res = np.zeros((len(sumweights), n2), dtype=field.dtype)
if get_variance:
var = np.zeros_like(res)

for i, fld in enumerate(field.reshape((n1, n2)).T):
try:
w = weights.flatten()
except AttributeError:
w = weights
if interpolation_method is None:
res[:, i] = _field_average(indx, fld, w, sumweights)

res[:, i] = _field_average(indx, fld, w, sumweights)

if get_variance:
var[:, i] = _field_variance(indx, fld, res[:, i], w, sumweights)
if get_variance:
var[:, i] = _field_variance(indx, fld, res[:, i], w, sumweights)
elif interpolation_method == "linear":
res[:, i], sumweights = _field_average_interpolate(coords, fld, bins, w)
if get_variance:

Check warning on line 430 in src/powerbox/tools.py

View check run for this annotation

Codecov / codecov/patch

src/powerbox/tools.py#L428-L430

Added lines #L428 - L430 were not covered by tests
# TODO: Implement variance calculation for interpolation
var[:, i] = np.zeros_like(res[:, i])

Check warning on line 432 in src/powerbox/tools.py

View check run for this annotation

Codecov / codecov/patch

src/powerbox/tools.py#L432

Added line #L432 was not covered by tests

if not get_variance:
return res.reshape((len(sumweights),) + field.shape[n:]), bins
Expand Down Expand Up @@ -587,6 +656,7 @@ def get_power(
k_weights=1,
nthreads=None,
prefactor_fnc=None,
interpolation_method=None,
):
r"""
Calculate isotropic power spectrum of a field, or cross-power of two similar fields.
Expand Down Expand Up @@ -772,6 +842,7 @@ def get_power(
get_variance=get_variance,
log_bins=log_bins,
weights=k_weights,
interpolation_method=interpolation_method,
)
res = list(res)
# Remove shot-noise
Expand Down

0 comments on commit 4372041

Please sign in to comment.