from .basefuncs import *
[docs]class HistOfX:
"""Class for one-dimensional histograms with nbins+1 linear bins edges in funcofx of array xarr computed in
initialization, except if edges are given.
Parameters
----------
xarr : ndarray
(N,) array to bin.
xrange : (float, float) or (`None`, `None`)
Range on which to bin xarr. If (`None`, `None`), chosen by function xrange_from_xarr. Not used if edges is not
`None`.
nbins : int, default = 128
Number of bins. Not used if edges is not `None`.
funcofx : weights: ndarray, optionaldefault = :func:`~galaximview.basefuncs.identity_function`
Function such that funcofx(xarr) is binned.
edges : ndarray or `None`, default = `None`
Edges of histogram. Used if given. If `None`, edges set with xrange and nbins by function
:func:`~galaximview.classeshists.linearly_spaced_edges_in_function_from_bin_size`, so that they are linearly
spaced in funcofx.
fill_range : bool, default = ` False`
If True and edges is not `None`, bins are added to cover full range of xarr. Used to keep a constant bin width.
"""
def __init__(self, xarr, xrange, nbins=128, funcofx=identity_function, edges=None, fill_range=False):
CheckArrays.check_masslike_array(xarr)
self.funcofx = funcofx
self.xarr = xarr
if (edges is not None):
if fill_range:
self.edges = fill_range_around_edges(xarr, funcofx, edges)
else:
self.edges = edges
else:
xrange = xrange_from_xarr(xarr, xrange, funcofx)
self.edges = linearly_spaced_edges_in_function_from_nb(xrange, funcofx, nbins)
[docs] def get_midbins(self):
"""Returns middle-points of (nbins+1) edges computed in self.__init__.
Returns
-------
ndarray
(nbins,) middle points.
"""
return 0.5 * (self.edges[1:] + self.edges[:-1])
[docs] def get_bins_widths(self):
"""Returns width of bins of (nbins+1) edges computed in self.__init__.
Returns
-------
ndarray
(nbins,) widths.
"""
return self.edges[1:] - self.edges[:-1]
[docs] def hist_of_f_of_x(self, weights=None):
"""Returns middle-points of nbins bins of (nbins+1,) edges computed in self.__init__ and number of array members
of funcofx(xarr) in bins if weights is `None`, or else sum of weights of array members of funcofx(xarr) in bins.
Parameters
----------
weights : ndarray, optional
(N,) weights.
Returns
-------
(ndarray, ndarray)
(nbins,) bins middle-points and histogram of funcofx(xarr).
"""
if (weights is None):
weights = np.ones_like(self.xarr)
else:
CheckArrays.check_masslike_array(weights)
if np.shape(self.xarr)[0] != np.shape(weights)[0]:
raise ValueError("Should be given arrays of same shape (N,).")
hm, ed = np.histogram(self.funcofx(self.xarr), self.edges, weights=weights)
x = self.get_midbins()
return x, hm
[docs] def binned_y_of_f_of_x(self, tobin, weights=None):
"""Returns middle-points of nbins bins of (nbins+1,) edges computed in self.__init__ and average of
tobin array in bins, weighted if weights is not `None`.
Parameters
----------
tobin : ndarray
(N,) array to bin.
weights : ndarray, optional
(N,) weights.
Returns
-------
(ndarray, ndarray)
(nbins,) bins middle-points and mass-weighted average of tobin array in bins.
"""
CheckArrays.check_masslike_array(tobin)
if (weights is None):
weights = np.ones_like(tobin)
CheckArrays.check_masslike_array(weights)
if np.shape(tobin)[0] != np.shape(weights)[0]:
raise ValueError("Should be given arrays of same shape (N,).")
rad = self.funcofx(self.xarr)
h, ed = np.histogram(rad, self.edges, weights=tobin * weights)
x, hm = self.hist_of_f_of_x(weights=weights)
inds0 = hm == 0
h[~inds0] /= hm[~inds0]
h[inds0] = np.nan
return x, h
[docs] def binned_dispersion_of_y_of_f_of_x(self, tobin, weights=None):
"""Returns middle-points of bins and dispersion of tobin array in bins, weighted if weights is not `None`.
Parameters
----------
tobin : ndarray
(N,) array to bin.
weights : ndarray, optional
(N,) weights.
Returns
-------
(ndarray, ndarray)
(nbins,) bins middle-points and weighted dispersion of tobin array in bins.
"""
CheckArrays.check_masslike_array(tobin)
if (weights is None):
weights = np.ones_like(tobin)
CheckArrays.check_masslike_array(weights)
if np.shape(tobin)[0] != np.shape(weights)[0]:
raise ValueError("Should be given arrays of same shape (N,).")
x, hofavsquares = self.binned_y_of_f_of_x(tobin ** 2, weights)
x, hav = self.binned_y_of_f_of_x(tobin, weights)
h = hofavsquares - hav ** 2
inds0 = h <= 0
h[~inds0] = np.sqrt(h[~inds0])
h[inds0] = np.nan
return x, h
[docs] def volumes_of_bin(self, volstr):
"""Attributes a "volume" to bins from their edges:
'width' for the normal width, (left edge) - (right edge)
'annulus_surface' for pi * (right edge) ** 2 - (left edge) ** 2
'spherical_shell' for 4/3 * pi * (right edge) ** 3 - (left edge) ** 3
Parameters
----------
volstr : {'width', 'annulus_surface', 'spherical_shell'}
Type of cell volume.
Returns
-------
ndarray
(nbins,) array of cells 'volume'.
"""
edges = inverse_function(self.funcofx, self.edges)
if (volstr == 'width'):
voldr = edges[1:] - edges[:-1]
elif (volstr == 'annulus_surface'):
voldr = edges[1:] ** 2 - edges[:-1] ** 2
voldr *= np.pi
elif (volstr == 'spherical_shell'):
voldr = edges[1:] ** 3 - edges[:-1] ** 3
voldr *= 4. / 3. * np.pi
else:
ValueError("Unknown volstr.")
voldr = 0
return voldr
[docs] def volumic_hist_of_f_of_x(self, weights=None, volstr='width'):
"""Computes a histogram (weighted if weights is not `None`) of func(xarr) divided by the "volume" of bins.
Parameters
----------
weights : ndarray, optional
(N,) weights.
volstr : {'width', 'annulus_surface', 'spherical_shell'}
Type of bin volume. See `~galaximview.classeshists.HistOfX.volumes_of_bin`.
Returns
-------
(ndarray, ndarray)
(nbins,) bins middle-points and histogram of funcofx(xarr) divided by cells 'volume'.
"""
if (weights is None):
weights = np.ones_like(self.xarr)
else:
CheckArrays.check_masslike_array(weights)
if np.shape(self.xarr)[0] != np.shape(weights)[0]:
raise ValueError("Should be given arrays of same shape (N,).")
voldr = self.volumes_of_bin(volstr)
rad = self.funcofx(self.xarr)
h, ed = np.histogram(rad, self.edges, weights=weights)
h /= voldr
return self.get_midbins(), h
[docs]class Hist2DOfXY:
"""Class for one-dimensional histograms with nbins+1 linear bins edges in funcofx of array xarr computed in
initialization, except if edges are given.
Parameters
----------
xcoords : ndarray
(N,) array to bin.
ycoords : ndarray
(M,) array to bin.
xrange : (float, float) or (None, None)
Range on which to bin xcoords. If (None, None), chosen by function xrange_from_xarr. Not used if xedges and
yedges are not None.
yrange : (float, float) or (None, None)
Range on which to bin ycoords. If (None, None), chosen by function xrange_from_xarr. Not used if xedges and
yedges are not None.
xedges : ndarray or None, optional
Edges of histogram. Used if given. If None, xedges set with xrange and nbx by function
func:`~galaximview.classeshists.linearly_spaced_edges_in_function_from_bin_size`, so that they are linearly
spaced in funcofx.
yedges : ndarray or None, optional
Edges of histogram. Used if given. If None, yedges set with yrange and nby by function
func:`~galaximview.classeshists.linearly_spaced_edges_in_function_from_bin_size`, so that they are linearly
spaced in funcofy.
"""
def __init__(self, xcoords, ycoords, xrange, yrange, nbx=128, nby=128, funcofx=identity_function,
funcofy=identity_function, yedges=None, xedges=None):
CheckArrays.check_masslike_array(xcoords)
CheckArrays.check_masslike_array(ycoords)
self.xcoords = xcoords
self.ycoords = ycoords
self.funcofx = funcofx
self.funcofy = funcofy
if ((xedges is not None) & (yedges is not None)):
self.xedges = xedges
self.yedges = yedges
else:
xrange = xrange_from_xarr(xcoords, xrange, funcofx)
yrange = xrange_from_xarr(ycoords, yrange, funcofy)
if (nby is None):
nby = int((yrange[1] - yrange[0]) / (xrange[1] - xrange[0]) * nbx)
self.xedges = linearly_spaced_edges_in_function_from_nb(xrange, funcofx, nbx)
self.yedges = linearly_spaced_edges_in_function_from_nb(yrange, funcofy, nby)
[docs] def get_x_midbins(self):
"""Returns middle-points of (nbins+1) x edges computed in self.__init__.
Returns
-------
ndarray
(nbins,) middle points."""
return 0.5 * (self.xedges[1:] + self.xedges[:-1])
[docs] def get_y_midbins(self):
"""Returns middle-points of (nbins+1) y edges computed in self.__init__.
Returns
-------
ndarray
(nbins,) middle points."""
return 0.5 * (self.yedges[1:] + self.yedges[:-1])
[docs] def get_x_binswidth(self):
"""Returns width of bins of (nbx+1) edges computed in self.__init__.
Returns
-------
ndarray
(nbx,) widths.
"""
return self.xedges[1:] - self.xedges[:-1]
[docs] def get_y_binswidth(self):
"""Returns width of bins of (nby+1) edges computed in self.__init__.
Returns
-------
ndarray
(nby,) widths.
"""
return self.yedges[1:] - self.yedges[:-1]
[docs] def get_xy_grid(self):
"""Returns meshgrid of bins middle-points.
Returns
-------
ndarray
(nbx, nby)
"""
midx = self.get_x_midbins()
midy = self.get_y_midbins()
return np.meshgrid(midx, midy)
[docs] def get_x_grid(self):
"""Returns x part of meshgrid of bins middle-points."""
return self.get_xy_grid()[0]
[docs] def get_y_grid(self):
"""Returns y part of meshgrid of bins middle-points."""
return self.get_xy_grid()[1]
[docs] def hist2d(self, weights=None):
"""Returns histogram and middle-points of nby bins of (nby+1,) yedges and of nbx bins of (nbx+1,) xedges,
and number of points of funcofx(xcoords) and funcofy(ycoords) in 2D bins if weights is None, or else sum of
weights of points.
Parameters
----------
weights: ndarray, optional
(N,) weights.
Returns
-------
(ndarray, ndarray, ndarray)
(nby, nbx) histogram, (nby,) y bins middle-points and (nbx,) x bins middle-points.
"""
if (weights is None):
weights = np.ones_like(self.xcoords)
else:
CheckArrays.check_masslike_array(weights)
if np.shape(self.xcoords)[0] != np.shape(weights)[0]:
raise ValueError("Should be given arrays of same shape (N,).")
xcoords = self.funcofx(self.xcoords)
ycoords = self.funcofy(self.ycoords)
hm2d, edy, edx = np.histogram2d(ycoords, xcoords, [self.yedges, self.xedges], weights=weights)
return hm2d, edy, edx
[docs] def binned_in_2d_array(self, tobin, weights=None):
"""Returns average of tobin array in 2d bins, weighted if weights is not None, middle-points of y bins and of
x bins.
Parameters
----------
tobin : ndarray
(N,) array to bin.
weights : ndarray, optional
(N,) weights.
Returns
-------
(ndarray, ndarray, ndarray)
(nby, nbx) histogram, (nby,) y bins middle-points and (nbx,) x bins middle-points.
"""
CheckArrays.check_masslike_array(tobin)
if (weights is None):
weights = np.ones_like(tobin)
CheckArrays.check_masslike_array(weights)
if np.shape(tobin)[0] != np.shape(weights)[0]:
raise ValueError("Should be given arrays of same shape (N,).")
xcoords = self.funcofx(self.xcoords)
ycoords = self.funcofy(self.ycoords)
h2d, edy, edx = np.histogram2d(ycoords, xcoords, [self.yedges, self.xedges], weights=tobin * weights)
hm2d, edy, edx = self.hist2d(weights=weights)
indsnonzero = (hm2d != 0)
h2d[indsnonzero] /= hm2d[indsnonzero]
h2d[~indsnonzero] = np.nan
return h2d, edy, edx
[docs] def binned_in_2d_dispersion_of_array(self, tobin, weights=None):
"""Returns dispersion of tobin array in 2d bins, weighted if weights is not None, y edges and x edges.
Parameters
----------
tobin : ndarray
(N,) array to bin.
weights : ndarray, optional
(N,) weights.
Returns
-------
(ndarray, ndarray, ndarray)
(nby, nbx) histogram, (nby,) y bins middle-points and (nbx,) x bins middle-points.
"""
CheckArrays.check_masslike_array(tobin)
if (weights is None):
weights = np.ones_like(tobin)
CheckArrays.check_masslike_array(weights)
if np.shape(tobin)[0] != np.shape(weights)[0]:
raise ValueError("Should be given arrays of same shape (N,).")
hofavsquares, edy, edx = self.binned_in_2d_array(tobin ** 2, weights=weights)
hav, edy, edx = self.binned_in_2d_array(tobin, weights=weights)
h = hofavsquares - hav ** 2
h = np.sqrt(h)
return h, edy, edx
[docs] def volumes_of_bins(self, volstrx, volstry):
"""Attributes a "volume" to 2D bins from their edges:
'width' for the normal width, (left edge) - (right edge) of x or y dimension
'annulus_surface' for pi * (right edge) ** 2 - (left edge) ** 2 of x or y dimension
'spherical_shell' for 4/3 * pi * (right edge) ** 3 - (left edge) ** 3 of x or y dimension
Parameters
----------
volstrx : {'width', 'annulus_surface', 'spherical_shell'}
Type of cell volume.
volstry : {'width', 'annulus_surface', 'spherical_shell'}
Type of cell volume.
Returns
-------
ndarray
(nby,nbx) array of bins 'volume'.
"""
xedges = inverse_function(self.funcofx, self.xedges)
yedges = inverse_function(self.funcofy, self.yedges)
if (volstrx == 'width'):
xl = xedges[1:] - xedges[:-1]
elif (volstrx == 'annulus_surface'):
xl = np.pi * (xedges[1:] ** 2 - xedges[:-1] ** 2)
elif (volstrx == 'spherical_shell'):
xl = 4. / 3. * np.pi * (xedges[1:] ** 3 - xedges[:-1] ** 3)
else:
print("unknown volstrx")
xl = 0
if (volstry == 'width'):
yl = yedges[1:] - yedges[:-1]
elif (volstry == 'annulus_surface'):
yl = np.pi * (yedges[1:] ** 2 - yedges[:-1] ** 2)
elif (volstry == 'spherical_shell'):
yl = 4. / 3. * np.pi * (yedges[1:] ** 3 - yedges[:-1] ** 3)
else:
print("unknown volstry")
yl = 0
surf = yl.reshape((-1, 1)) * xl
return surf
[docs] def volumic_hist2d(self, weights=None, volstrx='width', volstry='width'):
"""Computes a histogram (weighted if weights is not None) divided by the "volume" of bins.
Parameters
----------
weights : ndarray, optional
(N,) weights.
volstrx : {'width', 'annulus_surface', 'spherical_shell'}
Type of bin volume. See `~galaximview.classeshists.Hist2DOfXY.volumes_of_bin`.
volstry : {'width', 'annulus_surface', 'spherical_shell'}
Type of bin volume. See `~galaximview.classeshists.Hist2DOfXY.volumes_of_bin`.
Returns
-------
(ndarray, ndarray, ndarray)
(nby, nbx) histogram divided by cells 'volumes', (nby,) y bins middle-points and (nbx,) x bins
middle-points.
"""
if (weights is None):
weights = np.ones_like(self.xcoords)
else:
CheckArrays.check_masslike_array(weights)
if np.shape(self.xcoords)[0] != np.shape(weights)[0]:
raise ValueError("Should be given arrays of same shape (N,).")
surf = self.volumes_of_bins(volstrx, volstry)
hm2d, edy, edx = self.hist2d(weights=weights)
hm2d /= surf
return hm2d, edy, edx
[docs]class CicHistOfX(HistOfX):
"""Class for one-dimensional cloud-in-cell histograms with nb+1 linear bins edges from rmin to rmax."""
def __init__(self, rad, xrange, nbins=128):
HistOfX.__init__(self, rad, xrange, nbins=nbins, edges=None)
self.lcell = self.edges[1] - self.edges[0]
self.xg = self.xarr - 0.5 * self.lcell
self.xd = self.xarr + 0.5 * self.lcell
self.lg = np.floor(self.xg / self.lcell) + 1 - self.xg / self.lcell
self.ld = self.xd / self.lcell - np.floor(self.xd / self.lcell)
[docs] def hist_of_f_of_x(self, weights=None):
"""Returns middle-points of bins and CIC histogram.
Parameters
----------
weights
"""
if (weights is None):
weights = np.ones_like(self.xarr)
else:
CheckArrays.check_masslike_array(weights)
if np.shape(self.xarr)[0] != np.shape(weights)[0]:
raise ValueError("Should be given arrays of same shape (N,).")
hg, ed = np.histogram(self.xg, self.edges, weights=self.lg * weights)
hd, ed = np.histogram(self.xd, self.edges, weights=self.ld * weights)
x = self.get_midbins()
return x, hg + hd
[docs] def binned_y_of_f_of_x(self, tobin, weights=None):
"""Returns middle-points of bins and mass-weighted average of tobin array in rad bins.
Parameters
----------
weights : ndarray
(N,) masses.
tobin :
Returns
-------
"""
CheckArrays.check_masslike_array(tobin)
if (weights is None):
weights = np.ones_like(tobin)
CheckArrays.check_masslike_array(weights)
if np.shape(tobin)[0] != np.shape(weights)[0]:
raise ValueError("Should be given arrays of same shape (N,).")
hg, ed = np.histogram(self.xg, self.edges, weights=self.lg * weights * tobin)
hd, ed = np.histogram(self.xd, self.edges, weights=self.ld * weights * tobin)
x, hm = self.hist_of_f_of_x(weights=weights)
h = (hg + hd) / hm
return x, h
[docs] def binned_dispersion_of_y_of_f_of_x(self, tobin, weights=None):
"""Returns middle-points of bins and mass-weighted dispersion of tobin array in rad bins.
Parameters
----------
weights : ndarray
(N,) masses.
tobin :
Returns
-------
"""
CheckArrays.check_masslike_array(tobin)
if (weights is None):
weights = np.ones_like(tobin)
CheckArrays.check_masslike_array(weights)
if np.shape(tobin)[0] != np.shape(weights)[0]:
raise ValueError("Should be given arrays of same shape (N,).")
x, hofavsquares = self.binned_y_of_f_of_x(tobin ** 2, weights)
x, hav = self.binned_y_of_f_of_x(tobin, weights=weights)
h = hofavsquares - hav ** 2
h = np.sqrt(h)
return x, h
[docs]class CicHist2DOfXY(Hist2DOfXY):
"""Class for two-dimensional cloud-in-cell histograms with nbx,y+1 linear bins edges from x,ymin to x,ymax."""
def __init__(self, xcoords, ycoords, xrange, yrange, nbx=128, nby=128):
Hist2DOfXY.__init__(self, xcoords, ycoords, xrange, yrange, nbx=nbx, nby=nby, xedges=None, yedges=None)
self.lxcell = self.xedges[1] - self.xedges[0]
self.lycell = self.yedges[1] - self.yedges[0]
self.xleft = xcoords - 0.5 * self.lxcell
self.xright = xcoords + 0.5 * self.lxcell
self.ybottom = ycoords - 0.5 * self.lycell
self.ytop = ycoords + 0.5 * self.lycell
self.lxleft = np.floor(self.xleft / self.lxcell) + 1 - self.xleft / self.lxcell
self.lxright = self.xright / self.lxcell - np.floor(self.xright / self.lxcell)
self.lybottom = np.floor(self.ybottom / self.lycell) + 1 - self.ybottom / self.lycell
self.lytop = self.ytop / self.lycell - np.floor(self.ytop / self.lycell)
[docs] def cic2d(self, wght):
"""Computes weighted CIC histogram.
Parameters
----------
wght :
Returns
-------
"""
hgb, edy, edx = np.histogram2d(self.ybottom, self.xleft, [self.yedges, self.xedges],
weights=self.lxleft * self.lybottom * wght)
hgh, edy, edx = np.histogram2d(self.ytop, self.xleft, [self.yedges, self.xedges],
weights=self.lxleft * self.lytop * wght)
hdb, edy, edx = np.histogram2d(self.ybottom, self.xright, [self.yedges, self.xedges],
weights=self.lxright * self.lybottom * wght)
hdh, edy, edx = np.histogram2d(self.ytop, self.xright, [self.yedges, self.xedges],
weights=self.lxright * self.lytop * wght)
return hgb + hgh + hdb + hdh, edy, edx
[docs] def hist2d(self, weights=None):
"""Returns histogram and middle points of y,x bins.
Parameters
----------
weights
"""
if (weights is None):
weights = np.ones_like(self.xleft)
return self.cic2d(weights)
[docs] def binned_in_2d_array(self, tobin, weights=None):
"""Returns mass-weighted average of tobin array in bins and middle points of y,x bins.
Parameters
----------
weights : ndarray
(N,) masses.
tobin :
Returns
-------
"""
wght = weights * tobin
h, edy, edx = self.cic2d(wght)
hm, edy, edx = self.hist2d(weights=weights)
indsnonzero = (hm != 0)
h[indsnonzero] /= hm[indsnonzero]
h[~indsnonzero] = np.nan
return h, edy, edx
[docs]def xrange_from_xarr(xarr, xrange, funcofx):
"""Returns xrange if not (None, None) or else chooses reasonable range according to chosen function of x.
Parameters
----------
xarr : ndarray
(N,) array.
xrange : (float, float) or (None, None)
(xmin,xmax) returned as such if not (None, None).
funcofx : ufunc
Function such that the histogram will be of funcofx(xarr).
Returns
-------
(float, float)
Rangeon which the histogram of funcofx(xarr) will be computed.
"""
xmin = xrange[0]
xmax = xrange[1]
if ((xmin is None) | (xmax is None)):
if ((funcofx == np.log10) | (funcofx == np.log) | (funcofx == np.sqrt)):
proper_indexes = xarr > 0
else:
proper_indexes = np.fabs(funcofx(xarr)) != np.inf
xmin = np.nanmin(xarr[proper_indexes])
xmax = np.nanmax(xarr[proper_indexes])
if (xmin == xmax):
xmin = 0.9 * xmin
xmax = 1.1 * xmax
return (xmin, xmax)
[docs]def linearly_spaced_edges_in_function_from_nb(xrange, function, nb):
"""Returns edges linearly spaced in function, i.e.
function(xmin) + np.arange(nb + 1) * (function(xmax) - function(xmin)) / (nb)"
Parameters
----------
xrange : (float, float)
(xmin, xmax)
nb : int
Number of bins.
function : ufunc
Function such that unction(xmin) + np.arange(nb + 1) * (function(xmax) - function(xmin)) / (nb) is returned.
Returns
-------
ndarray
(nb+1,) edges linearly spaced in function.
"""
funcofx = function(xrange[0]) + np.arange(nb + 1) * (function(xrange[1]) - function(xrange[0])) / (nb)
return funcofx
[docs]def linearly_spaced_edges_in_function_from_bin_size(xrange, function, bin_size):
"""Returns edges linearly spaced in function, i.e.
function(xmin) + np.arange(nb + 1) * (function(xmax) - function(xmin)) / (nb)"
Parameters
----------
xrange : (float, float)
(xmin, xmax)
bin_size : float
Width of bins.
function : ufunc
Function such that unction(xmin) + np.arange(nb + 1) * (function(xmax) - function(xmin)) / (nb) is returned.
Returns
-------
ndarray
(nb+1,) edges linearly spaced in function.
"""
nb = int((function(xrange[1]) - function(xrange[0])) / bin_size) + 1 # at worst, one useless bin in the end
funcofx = function(xrange[0]) + np.arange(nb + 1) * bin_size
return funcofx
[docs]def fill_range_around_edges(arr, func, edges):
"""Fills range of func(arr) with additional bins if edges do not cover the whole range.
Parameters
----------
arr : ndarray
(N,) array.
func : ufunc
Function such that bins in func(arr) are computed.
edges : ndarray
Edges.
Returns
-------
ndarray
Edges covering the whole range of func(arr), linearly space in func(arr).
"""
xrange = xrange_from_xarr(arr, (None, None), func)
xrange = func(xrange)
bin_size = edges[1] - edges[0]
if (xrange[0] < edges[0]):
nb_left = int((edges[0] - xrange[0]) / bin_size) + 1
left_edges = edges[0] - bin_size - np.arange(nb_left) * bin_size
left_edges = left_edges[::-1]
edges = np.concatenate((left_edges, edges))
if (xrange[1] > edges[-1]):
nb_right = int((xrange[1] - edges[-1]) / bin_size) + 1
right_edges = edges[-1] + bin_size + np.arange(nb_right) * bin_size
edges = np.concatenate((edges, right_edges))
return edges