Skip to content

Commit 78f7ed8

Browse files
committed
Pre-allocate the output array
1 parent 7ae6e47 commit 78f7ed8

1 file changed

Lines changed: 272 additions & 9 deletions

File tree

kingmaker/wrapper.py

Lines changed: 272 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def __init__(
8181
# Set some default values for the event-level parameters.
8282
self.event_distances, self.map_index = [], []
8383
self.event_pvalue = {}
84+
self._result_buffer: npt.NDArray[np.floating] = np.array([])
8485

8586
# Obtain the King distribution parameters for all bins. If we're caching parameters
8687
# and a cache file exists, load from the cache instead of fitting. Otherwise,
@@ -215,6 +216,15 @@ def set_events(
215216
self.event_mask = all_dists < cutoff
216217
self.event_distances = all_dists[self.event_mask]
217218

219+
self._result_buffer = np.zeros(len(events))
220+
221+
all_alpha, all_beta = self.get_alpha_beta(events, copy=False)
222+
for i, gamma in enumerate(self.spectral_indices):
223+
alpha, beta = self.get_alpha_beta_gamma(gamma, alpha=all_alpha, beta=all_beta)
224+
self.event_pvalue[gamma] = self.king_pdf.pdf(self.event_distances, alpha, beta)
225+
return
226+
227+
def get_alpha_beta(self, events, copy=True):
218228
# Nearest-bin lookup. Field-first masking (events[key][mask]) avoids
219229
# copying the full structured array before extracting each field.
220230
def index(centers, values):
@@ -226,13 +236,40 @@ def index(centers, values):
226236
for i, key in enumerate(self.keys)
227237
)
228238

229-
all_alpha = self.alpha_values[(slice(None), *event_indices)]
230-
all_beta = self.beta_values[(slice(None), *event_indices)]
231-
all_pvalues = self.king_pdf.pdf(self.event_distances, all_alpha, all_beta)
232-
for i, gamma in enumerate(self.spectral_indices):
233-
self.event_pvalue[gamma] = all_pvalues[i]
239+
alpha = self.alpha_values[(slice(None), *event_indices)]
240+
beta = self.beta_values[(slice(None), *event_indices)]
241+
return alpha, beta
234242

235-
return
243+
def get_alpha_beta_gamma(self, gamma, events=None, alpha=None, beta=None):
244+
if alpha is None:
245+
assert events is not None
246+
alpha, beta = self.get_alpha_beta(events, copy=False)
247+
assert len(alpha) == len(beta)
248+
249+
# If we have this gamma, just return it. Make sure to use copy()
250+
# so the caller doesn't get a reference into our array.
251+
if gamma in self.spectral_indices:
252+
gamma_idx = np.searchsorted(self.spectral_indices, gamma) - 1
253+
return alpha[gamma_idx].copy(), beta[gamma_idx].copy()
254+
255+
# Otherwise, we want to interpolate.
256+
gamma_low = max(np.searchsorted(self.spectral_indices, gamma) - 1, 0)
257+
gamma_high = min(gamma_low, len(self.spectral_indices - 1))
258+
259+
return (
260+
_interp1d(
261+
self.spectral_indices[gamma_low],
262+
self.spectral_indices[gamma_high],
263+
alpha[gamma_low],
264+
alpha[gamma_high],
265+
),
266+
_interp1d(
267+
self.spectral_indices[gamma_low],
268+
self.spectral_indices[gamma_high],
269+
beta[gamma_low],
270+
beta[gamma_high],
271+
),
272+
)
236273

237274
def evaluate_pdf(self, events: npt.NDArray[Any], gamma: float = 2) -> npt.NDArray[np.floating]:
238275
# If we haven't already calculated the per-event alpha and beta parameters, do so now.
@@ -248,12 +285,238 @@ def evaluate_pdf(self, events: npt.NDArray[Any], gamma: float = 2) -> npt.NDArra
248285
)
249286

250287
gamma_low, gamma_high = self.spectral_indices[idx], self.spectral_indices[idx + 1]
251-
result = np.zeros(len(self.events))
252-
result[self.event_mask] = _interp1d(
288+
self._result_buffer[:] = 0.0
289+
self._result_buffer[self.event_mask] = _interp1d(
253290
gamma,
254291
gamma_low,
255292
gamma_high,
256293
self.event_pvalue[gamma_low],
257294
self.event_pvalue[gamma_high],
258295
)
259-
return result
296+
return self._result_buffer
297+
298+
299+
# class KingTemplateLikelihood:
300+
# # Configuration parameters
301+
# parametrization_bins: Dict[str, npt.NDArray[np.floating]]
302+
# spectral_indices: npt.NDArray[np.floating]
303+
# angular_cutoff: float
304+
# cache_parameters: bool = True
305+
# cache_name: str = "king_parameters_cache.npz"
306+
307+
# # Store an instance of the PDF class to use for evaluations. This will be
308+
# # either a KingPDF for standard point source searches
309+
# king_pdf: KingTemplatePDF
310+
311+
# # Source-level information
312+
# skymap: npt.NDArray[np.floating]
313+
# convolution_dtype: Any = np.float32
314+
# convolution_alphas: npt.NDArray[np.floating]
315+
# convolution_alphas: npt.NDArray[np.floating]
316+
# convolved_skymaps: npt.NDArray[np.floating]
317+
318+
# # Have some place to cache the per-event information so we don't need to
319+
# # recalculate it every time we evaluate the PDF.
320+
# events: Optional[Any] = None
321+
# event_convolution_indices = np.NDArray[np.integer]
322+
323+
# def __init__(
324+
# self,
325+
# skymap: npt.NDArray[np.floating],
326+
# signal_events: npt.NDArray[Any],
327+
# parametrization_bins: Dict[str, Union[int, List, Tuple, npt.NDArray]],
328+
# convolution_nside : int = 128,
329+
# convolution_alphas : Union[npt.NDArray, int] = 10,
330+
# convolution_betas : Union[npt.NDArray, int] = 10,
331+
# convolution_dtype : Any = np.float32,
332+
# dpsi_nbins: int = 101,
333+
# minimum_counts: int = 100,
334+
# spectral_indices: Union[List[float], npt.NDArray[np.floating]] = [
335+
# 1.0,
336+
# 2.0,
337+
# 3.0,
338+
# 4.0,
339+
# ],
340+
# angular_cutoff: float = np.pi,
341+
# cache_parameters: bool = True,
342+
# cache_name: str = "./king_parameters_cache.npz",
343+
# remove_weight_outliers=True,
344+
# weight_outlier_percentiles=(0, 95),
345+
# weight_field: str = "ow",
346+
# true_ra_name: str = "trueRa",
347+
# true_dec_name: str = "trueDec",
348+
# true_energy_name: str = "trueE",
349+
# ):
350+
# # Store some of the configuration parameters for this instance.
351+
# # Note that we don't need to store the signal events, dpsi_nbins,
352+
# # or minimum counts since they're only necessary for fitting during
353+
# # initialization andnot for later evaluation. We'll also be storing
354+
# # the parametrization bins later, since the user may have simply
355+
# # passed in a number of bins instead of actual bin edges.
356+
# self.spectral_indices = np.atleast_1d(spectral_indices)
357+
358+
# # Set some default values for the event-level parameters.
359+
# self.event_pvalue = {}
360+
361+
# # Obtain the King distribution parameters for all bins. If we're caching parameters
362+
# # and a cache file exists, load from the cache instead of fitting. Otherwise,
363+
# # run the fitter and potentially cache the results. Note that if we run the fitter,
364+
# # we explicitly set the angular cutoff to pi: this is to ensure that we allow the
365+
# # full histogram to be fit for each bin without artificially setting the PDF to 0
366+
# # for some bins.
367+
# fitted_parameters: Dict[str, npt.NDArray[np.floating]] = {}
368+
# if cache_parameters and (cache_name is not None) and exists(cache_name):
369+
# fitted_parameters_npz = np.load(cache_name, allow_pickle=True)
370+
# for key in fitted_parameters_npz.files:
371+
# fitted_parameters[key] = fitted_parameters_npz[key]
372+
# else:
373+
# fitter = KingPSFFitter(
374+
# signal_events=signal_events,
375+
# parametrization_bins=parametrization_bins,
376+
# dpsi_nbins=dpsi_nbins,
377+
# minimum_counts=minimum_counts,
378+
# spectral_indices=spectral_indices,
379+
# angular_cutoff=np.pi,
380+
# remove_weight_outliers=remove_weight_outliers,
381+
# weight_outlier_percentiles=weight_outlier_percentiles,
382+
# weight_field=weight_field,
383+
# true_ra_name=true_ra_name,
384+
# true_dec_name=true_dec_name,
385+
# true_energy_name=true_energy_name,
386+
# )
387+
# fitted_parameters = fitter.fit_all_bins(verbose=True)
388+
# if cache_parameters and (cache_name is not None):
389+
# np.savez(cache_name, **fitted_parameters) # type: ignore[arg-type]
390+
391+
# # Store the fitted parameters and bins for later interpolation during PDF evaluation.
392+
# self.parametrization_bins = fitted_parameters["parametrization_bins"] # type: ignore[assignment]
393+
# try:
394+
# self.parametrization_bins.items()
395+
# except AttributeError:
396+
# self.parametrization_bins = self.parametrization_bins.item()
397+
398+
# # Extract the bin centers and keys for each event. The stored bins are
399+
# # edges, but interpn requires coordinates matching the values shape.
400+
# self.keys, self.bin_centers = [], []
401+
# for key, edges in self.parametrization_bins.items():
402+
# self.keys.append(key)
403+
# self.bin_centers.append((edges[:-1] + edges[1:]) / 2)
404+
405+
# # And grab the fitted alpha/beta arrays
406+
# self.alpha_values = fitted_parameters["alpha"]
407+
# self.beta_values = fitted_parameters["beta"]
408+
409+
# # Instantiate the PDF object.
410+
# self.king_pdf = KingTemplatePDF(angular_cutoff=angular_cutoff)
411+
# self.convolution_nside = convolution_nside
412+
# self.convolution_alphas = np.sort(convolution_alphas)
413+
# self.convolution_betas = np.sort(convolution_betas)
414+
# self.convolution_dtype = convolution_dtype
415+
416+
# # We can now take the alpha/beta bins from the fitter and directly convert them to
417+
# # the correct indicies in convolution_alphas and convolution_betas.
418+
# # TODO: Should these be nearest-neighbors instead of flooring?
419+
# self.alpha_values_idx = np.searchsorted(self.convolution_alphas,
420+
# self.alpha_values) - 1
421+
# self.beta_values_idx = np.searchsorted(self.convolution_betas,
422+
# self.beta_values) - 1
423+
424+
# # We can also just do the convolutions now since we know the grid.
425+
# shape = (len(self.alpha_values), len(self.beta_values), hp.nside2npix(self.convolution_nsize))
426+
427+
# # Warn the user if this is more than 1 GB...
428+
# expected_size = np.prod(shape) * self.convolution_dtype().nbytes
429+
# if expected_size / 1024**3 > 1:
430+
# print(f"WARNING: Requested shape (gamma, alpha, beta, skymap) = {shape}"
431+
# f" with dtype {self.convolution_dtype}. This will give a total array"
432+
# f" size of {expected_size} GB.")
433+
# if expected_size / 1024**3 > 4:
434+
# raise MemoryError(f"Requested shape (gamma, alpha, beta, skymap) = {shape}"
435+
# f" with dtype {self.convlution_dtype} will have a total"
436+
# f" size of {expected_size}. This seems unreasonable, so"
437+
# " I'm kicking this back to you to reconsider.")
438+
439+
# self.convolved_skymaps = np.empty(shape, dtype=self.convolution_dtype)
440+
441+
# for index in np.nditer(shape[:-1]):
442+
# i, j = index
443+
# self.convolved_skymaps[i,j] = self.king_pdf.convolve_map(
444+
# self.alpha_values[i], self.beta_values[j])
445+
# return
446+
447+
# def _events_match(self, events: npt.NDArray[Any]) -> bool:
448+
# if self.events is None:
449+
# return False
450+
# if events is None:
451+
# return True
452+
# if len(self.events) != len(events):
453+
# return False
454+
# result = np.array_equal(self.events["ra"][::10], events["ra"][::10])
455+
# result &= np.array_equal(self.events["dec"][::10], events["dec"][::10])
456+
# return result
457+
458+
# def set_events(
459+
# self,
460+
# events: npt.NDArray[Any],
461+
# ) -> None:
462+
# """Calculate per-event pvalues for each spectral index by interpolating
463+
# the King-convolved templates at the nearest parametrization bin for each event.
464+
# """
465+
# if self._events_match(events):
466+
# return
467+
468+
# self.events = events
469+
470+
# # Make sure we have a matching number of source_ras and source_decs if we're given multiple sources.
471+
# if (source_ras is None) and (source_decs is None):
472+
# raise ValueError(
473+
# "No source_ras and source_decs were provided to the set_eventsfunction."
474+
# )
475+
# if (source_ras is None or source_decs is None) or (len(source_ras) != len(source_decs)):
476+
# raise ValueError(
477+
# "The number of source_ras and source_decs must match. Please ensure "
478+
# "that these arrays have the same length when passing into set_events."
479+
# )
480+
481+
# # Nearest-bin lookup. These map the events from their parametrization bins to
482+
# # the correct healpix bins. We'll then do the gamma lookup later.
483+
# def index(centers, values):
484+
# i = np.searchsorted(centers, values).clip(1, len(centers) - 1)
485+
# return np.where(values - centers[i - 1] < centers[i] - values, i - 1, i)
486+
487+
# event_indices = tuple(
488+
# index(self.bin_centers[i], events[key][self.event_mask])
489+
# for i, key in enumerate(self.keys)
490+
# )
491+
492+
# self.event_convolution_indices = np.empty((len(self.spectral_indices),
493+
# len(self.events)), dtype=int)
494+
# for i in range(len(self.spectral_indices)):
495+
# .......................
496+
497+
# return
498+
499+
500+
# def evaluate_pdf(self, events: npt.NDArray[Any], gamma: float = 2) -> npt.NDArray[np.floating]:
501+
# # If we haven't already calculated the per-event alpha and beta parameters, do so now.
502+
# if not self._events_match(events):
503+
# raise RuntimeError(
504+
# "The events provided to evaluate_pdf do not match the events that were used to calculate the per-event parameters."
505+
# " Please ensure that you call set_events with the same events that you later pass into evaluate_pdf."
506+
# )
507+
508+
# # Interpolate over gamma to get the final result for each event
509+
# idx = np.clip(
510+
# np.searchsorted(self.spectral_indices, gamma) - 1, 0, len(self.spectral_indices) - 2
511+
# )
512+
513+
# gamma_low, gamma_high = self.spectral_indices[idx], self.spectral_indices[idx + 1]
514+
# result = np.zeros(len(self.events))
515+
# result[self.event_mask] = _interp1d(
516+
# gamma,
517+
# gamma_low,
518+
# gamma_high,
519+
# self.event_pvalue[gamma_low],
520+
# self.event_pvalue[gamma_high],
521+
# )
522+
# return result

0 commit comments

Comments
 (0)