From bbed612139932fd5d937f1069a6fbc70f68af346 Mon Sep 17 00:00:00 2001 From: Andrew Hearin Date: Mon, 12 Mar 2018 15:27:03 -0600 Subject: [PATCH 1/5] Updated abunmatch sub-package with new bin-free CAM implementation --- .../empirical_models/abunmatch/__init__.py | 2 + .../abunmatch/bin_free_cam.py | 128 +++++ .../conditional_abunmatch_bin_based.py | 8 + .../abunmatch/engines/__init__.py | 1 + .../abunmatch/engines/bin_free_cam_kernel.pyx | 277 ++++++++++ .../abunmatch/engines/setup_package.py | 27 + .../abunmatch/tests/naive_python_cam.py | 43 ++ .../abunmatch/tests/test_bin_free_cam.py | 505 ++++++++++++++++++ .../tests/test_conditional_abunmatch.py | 6 +- .../abunmatch/tests/test_pure_python.py | 156 ++++++ .../tests/test_sample2_window_function.py | 121 +++++ .../abunmatch/tests/test_single_unit.py | 12 + 12 files changed, 1283 insertions(+), 3 deletions(-) create mode 100644 halotools/empirical_models/abunmatch/bin_free_cam.py create mode 100644 halotools/empirical_models/abunmatch/engines/__init__.py create mode 100644 halotools/empirical_models/abunmatch/engines/bin_free_cam_kernel.pyx create mode 100644 halotools/empirical_models/abunmatch/engines/setup_package.py create mode 100644 halotools/empirical_models/abunmatch/tests/naive_python_cam.py create mode 100644 halotools/empirical_models/abunmatch/tests/test_bin_free_cam.py create mode 100644 halotools/empirical_models/abunmatch/tests/test_pure_python.py create mode 100644 halotools/empirical_models/abunmatch/tests/test_sample2_window_function.py create mode 100644 halotools/empirical_models/abunmatch/tests/test_single_unit.py diff --git a/halotools/empirical_models/abunmatch/__init__.py b/halotools/empirical_models/abunmatch/__init__.py index 248ecf7c3..deb1bac75 100644 --- a/halotools/empirical_models/abunmatch/__init__.py +++ b/halotools/empirical_models/abunmatch/__init__.py @@ -1,2 +1,4 @@ from .conditional_abunmatch_bin_based import * from .noisy_percentile import * +from .engines import cython_bin_free_cam_kernel +from .bin_free_cam import conditional_abunmatch diff --git a/halotools/empirical_models/abunmatch/bin_free_cam.py b/halotools/empirical_models/abunmatch/bin_free_cam.py new file mode 100644 index 000000000..3449675c7 --- /dev/null +++ b/halotools/empirical_models/abunmatch/bin_free_cam.py @@ -0,0 +1,128 @@ +""" +""" +import numpy as np +from ...utils import unsorting_indices +from ...utils.conditional_percentile import _check_xyn_bounds, rank_order_function +from .engines import cython_bin_free_cam_kernel +from .tests.naive_python_cam import sample2_window_indices + + +def conditional_abunmatch(x, y, x2, y2, nwin, add_subgrid_noise=True, + assume_x_is_sorted=False, assume_x2_is_sorted=False): + r""" + Given a set of input points with primary property `x` and secondary property `y`, + use conditional abundance matching to map new values `ynew` onto the input points + such that :math:`P(>> npts1, npts2 = 5000, 3000 + >>> x = np.linspace(0, 1, npts1) + >>> y = np.random.uniform(-1, 1, npts1) + >>> x2 = np.linspace(0.5, 0.6, npts2) + >>> y2 = np.random.uniform(-5, 3, npts2) + >>> nwin = 51 + >>> new_y = conditional_abunmatch(x, y, x2, y2, nwin) + + Notes + ----- + The ``nwin`` argument controls the precision of the calculation, + and also the performance. For estimations of Prob(< y | x) with sub-percent accuracy, + values of ``window_length`` must exceed 100. Values more tha a few hundred are + likely overkill when using the (recommended) sub-grid noise option. + + See :ref:`cam_tutorial` demonstrating how to use this + function in galaxy-halo modeling with several worked examples. + + With the release of Halotools v0.7, this function replaced a previous function + of the same name. The old function is now called + `~halotools.empirical_models.conditional_abunmatch_bin_based`. + + """ + x, y, nwin = _check_xyn_bounds(x, y, nwin) + x2, y2, nwin = _check_xyn_bounds(x2, y2, nwin) + nhalfwin = int(nwin/2) + npts1 = len(x) + + if assume_x_is_sorted: + x_sorted = x + y_sorted = y + else: + idx_x_sorted = np.argsort(x) + x_sorted = x[idx_x_sorted] + y_sorted = y[idx_x_sorted] + + if assume_x2_is_sorted: + x2_sorted = x2 + y2_sorted = y2 + else: + idx_x2_sorted = np.argsort(x2) + x2_sorted = x2[idx_x2_sorted] + y2_sorted = y2[idx_x2_sorted] + + i2_matched = np.searchsorted(x2_sorted, x_sorted).astype('i4') + + result = np.array(cython_bin_free_cam_kernel( + y_sorted, y2_sorted, i2_matched, nwin, int(add_subgrid_noise))) + + # Finish the leftmost points in pure python + iw = 0 + for ix1 in range(0, nhalfwin): + iy2_low, iy2_high = sample2_window_indices(ix1, x_sorted, x2_sorted, nwin) + leftmost_sorted_window_y2 = np.sort(y2_sorted[iy2_low:iy2_high]) + leftmost_window_ranks = rank_order_function(y_sorted[:nwin]) + result[ix1] = leftmost_sorted_window_y2[leftmost_window_ranks[iw]] + iw += 1 + + # Finish the rightmost points in pure python + iw = nhalfwin + 1 + for ix1 in range(npts1-nhalfwin, npts1): + iy2_low, iy2_high = sample2_window_indices(ix1, x_sorted, x2_sorted, nwin) + rightmost_sorted_window_y2 = np.sort(y2_sorted[iy2_low:iy2_high]) + rightmost_window_ranks = rank_order_function(y_sorted[-nwin:]) + result[ix1] = rightmost_sorted_window_y2[rightmost_window_ranks[iw]] + iw += 1 + + if assume_x_is_sorted: + return result + else: + return result[unsorting_indices(idx_x_sorted)] diff --git a/halotools/empirical_models/abunmatch/conditional_abunmatch_bin_based.py b/halotools/empirical_models/abunmatch/conditional_abunmatch_bin_based.py index 52fdfe0d1..cd33f3f4e 100644 --- a/halotools/empirical_models/abunmatch/conditional_abunmatch_bin_based.py +++ b/halotools/empirical_models/abunmatch/conditional_abunmatch_bin_based.py @@ -93,6 +93,14 @@ def conditional_abunmatch_bin_based(haloprop, galprop, sigma=0., npts_lookup_tab see the `deconvolution abundance matching code `_ written by Yao-Yuan Mao. + With the release of Halotools v0.7, this function had its name changed. + The previous name given to this function was "conditional_abunmatch". + Halotools v0.7 has a new function `~halotools.empirical_models.conditional_abunmatch` + with this name that largely replaces the functionality here. + See :ref:`cam_tutorial` demonstrating how to use the new + function in galaxy-halo modeling with several worked examples. + + """ haloprop_table, galprop_table = its.build_cdf_lookup(galprop, npts_lookup_table) haloprop_percentiles = its.rank_order_percentile(haloprop) diff --git a/halotools/empirical_models/abunmatch/engines/__init__.py b/halotools/empirical_models/abunmatch/engines/__init__.py new file mode 100644 index 000000000..1d58f11e1 --- /dev/null +++ b/halotools/empirical_models/abunmatch/engines/__init__.py @@ -0,0 +1 @@ +from .bin_free_cam_kernel import cython_bin_free_cam_kernel diff --git a/halotools/empirical_models/abunmatch/engines/bin_free_cam_kernel.pyx b/halotools/empirical_models/abunmatch/engines/bin_free_cam_kernel.pyx new file mode 100644 index 000000000..1411b6ffd --- /dev/null +++ b/halotools/empirical_models/abunmatch/engines/bin_free_cam_kernel.pyx @@ -0,0 +1,277 @@ +""" +""" +from libc.stdlib cimport rand, RAND_MAX +from libc.math cimport floor +import numpy as np +cimport cython +from ....utils import unsorting_indices + +__all__ = ('cython_bin_free_cam_kernel', ) + + +cdef double random_uniform(): + cdef double r = rand() + return r / RAND_MAX + + +@cython.boundscheck(False) +@cython.nonecheck(False) +@cython.wraparound(False) +cdef int _bisect_left_kernel(double[:] arr, double value): + """ Return the index where to insert ``value`` in list ``arr`` of length ``n``, + assuming ``arr`` is sorted. + + This function is equivalent to the bisect_left function implemented in the + python standard libary bisect. + """ + cdef int n = arr.shape[0] + cdef int ifirst_subarr = 0 + cdef int ilast_subarr = n + cdef int imid_subarr + + while ilast_subarr-ifirst_subarr >= 2: + imid_subarr = (ifirst_subarr + ilast_subarr)/2 + if value > arr[imid_subarr]: + ifirst_subarr = imid_subarr + else: + ilast_subarr = imid_subarr + if value > arr[ifirst_subarr]: + return ilast_subarr + else: + return ifirst_subarr + + +@cython.boundscheck(False) +@cython.nonecheck(False) +@cython.wraparound(False) +cdef void _insert_first_pop_last_kernel(int* arr, int value_in1, int n): + """ Insert the element ``value_in1`` into the input array and pop out the last element + """ + cdef int i + for i in range(n-2, -1, -1): + arr[i+1] = arr[i] + arr[0] = value_in1 + + +@cython.boundscheck(False) +@cython.nonecheck(False) +@cython.wraparound(False) +cdef int _correspondence_indices_shift(int idx_in1, int idx_out1, int idx): + """ Update the correspondence indices array + """ + cdef int shift = 0 + if idx_in1 < idx_out1: + if idx_in1 <= idx < idx_out1: + shift = 1 + elif idx_in1 > idx_out1: + if idx_out1 < idx <= idx_in1: + shift = -1 + return shift + + +@cython.boundscheck(False) +@cython.nonecheck(False) +@cython.wraparound(False) +cdef void _insert_pop_kernel(double* arr, int idx_in1, int idx_out1, double value_in1): + """ Pop out the value stored in index ``idx_out1`` of array ``arr``, + and insert ``value_in1`` at index ``idx_in1`` of the final array. + """ + cdef int i + + if idx_in1 <= idx_out1: + for i in range(idx_out1-1, idx_in1-1, -1): + arr[i+1] = arr[i] + else: + for i in range(idx_out1, idx_in1): + arr[i] = arr[i+1] + arr[idx_in1] = value_in1 + + +def setup_initial_indices(iy2, nwin, npts2): + """ Search an array of length npts2 to identify + the unique window of width nwin centered iy2. Care is taken to deal with + the left and right edges. For elements iy2 < nwin/2, the first nwin elements + of the array are used; for elements iy2 > npts2-nwin/2, + the last nwin elements are used. + """ + nhalfwin = int(nwin/2) + init_iy2_low = iy2 - nhalfwin + init_iy2_high = init_iy2_low+nwin + + if init_iy2_low < 0: + init_iy2_low = 0 + init_iy2_high = init_iy2_low + nwin + iy2 = init_iy2_low + nhalfwin + elif init_iy2_high > npts2 - nhalfwin: + init_iy2_high = npts2 + init_iy2_low = init_iy2_high - nwin + iy2 = init_iy2_low + nhalfwin + + return iy2, init_iy2_low, init_iy2_high + + +@cython.boundscheck(False) +@cython.nonecheck(False) +@cython.wraparound(False) +def cython_bin_free_cam_kernel(double[:] y1, double[:] y2, int[:] i2_match, int nwin, + int add_subgrid_noise=0): + """ Kernel underlying the bin-free implementation of conditional abundance matching. + For the i^th element of y1, we define a window of length `nwin` surrounding the + point y1[i], and another window surrounding y2[i2_match[i]]. We calculate the + rank-order of y1[i] within the first window. Then we find the point in the second + window with a matching rank-order and use this value as ynew[i]. + The algorithm has been implemented so that the windows are only sorted once + at the beginning, and as the windows slide along the arrays with increasing i, + elements are popped in and popped out so preserve the sorted order. + + When using add_subgrid_noise, the algorithm differs slightly. Rather than setting + ynew[i] to the value in the second window with the matching rank-order, + instead we assign a random uniform number from the range spanned by + (y2_window[rank-1],y2_window[rank+1]). This eliminates discreteness effects + and comes at no loss of precision since the PDF is not known to an accuracy + better than 1/nwin. + + The arrays named sorted_cdf_values store the two windows. + The arrays correspondence_indx are responsible for the bookkeeping involved in + maintaining a sorted order as elements are popped in and popped out. + The way this works is that as the window slides along from left to right, + the leftmost value is the one that should be popped out + (that is, the y value corresponding to the smallest x in the window). + However, the position of this element within sorted_cdf_values can be anywhere. + So the correspondence_indx array is used to keep track of the x-ordering + of the values within the windows. + In particular, element 0 in correspondence_indx stores the position of the + most-recently added element to sorted_cdf_values; + element nwin-1 in correspondence_indx stores the position of the + element of sorted_cdf_values that will be the next one popped out; + element nwin/2 stores the position of the middle of the window within + sorted_cdf_values. Since the position within sorted_cdf_values is the rank, + then sorted_cdf_values[correspondence_indx[nwin/2]] stores the value of ynew. + + """ + cdef int nhalfwin = int(nwin/2) + cdef int npts1 = y1.shape[0] + cdef int npts2 = y2.shape[0] + + cdef int iy1, i, j, idx, idx2, iy2_match + cdef int idx_in1, idx_out1, idx_in2, idx_out2 + cdef double value_in1, value_out1, value_in2, value_out2 + + cdef double[:] y1_new = np.zeros(npts1, dtype='f8') - 1 + cdef int rank1, rank2 + + # Set up initial window arrays for y1 + cdf_values1 = np.copy(y1[:nwin]) + idx_sorted_cdf_values1 = np.argsort(cdf_values1) + cdef double[:] sorted_cdf_values1 = np.ascontiguousarray( + cdf_values1[idx_sorted_cdf_values1], dtype='f8') + cdef int[:] correspondence_indx1 = np.ascontiguousarray( + unsorting_indices(idx_sorted_cdf_values1)[::-1], dtype='i4') + + # Set up initial window arrays for y2 + cdef int iy2_init = i2_match[nhalfwin] + _iy2, init_iy2_low, init_iy2_high = setup_initial_indices( + iy2_init, nwin, npts2) + cdef int iy2 = _iy2 + cdef int iy2_max = npts2 - nhalfwin - 1 + + cdef int low_rank, high_rank + cdef double low_cdf, high_cdf + + # Ensure that any bookkeeping error in setting up the window + # is caught by an exception rather than a bus error + msg = ("Bookkeeping error internal to cython_bin_free_cam_kernel\n" + "init_iy2_low = {0}, init_iy2_high = {1}, nwin = {2}") + assert init_iy2_high - init_iy2_low == nwin, msg.format( + init_iy2_low, init_iy2_high, nwin) + + cdf_values2 = np.copy(y2[init_iy2_low:init_iy2_high]) + + idx_sorted_cdf_values2 = np.argsort(cdf_values2) + + cdef double[:] sorted_cdf_values2 = np.ascontiguousarray( + cdf_values2[idx_sorted_cdf_values2], dtype='f8') + cdef int[:] correspondence_indx2 = np.ascontiguousarray( + unsorting_indices(idx_sorted_cdf_values2)[::-1], dtype='i4') + + # Loop over elements of the first array, ignoring the first and last nwin/2 points, + # which will be treated separately by the python wrapper. + for iy1 in range(nhalfwin, npts1-nhalfwin): + + rank1 = correspondence_indx1[nhalfwin] + iy2_match = i2_match[iy1] + + # Stop updating the second window once we reach npts2-nwin/2 + if iy2_match > iy2_max: + iy2_match = iy2_max + + if iy2 > iy2_max: + iy2 = iy2_max + else: + # Continue to slide the window along the second array + # until we find the matching point, updating the window with each iteration + while iy2 < iy2_match: + + # Find the value coming in and the value coming out + value_in2 = y2[iy2 + nhalfwin + 1] + idx_out2 = correspondence_indx2[nwin-1] + value_out2 = sorted_cdf_values2[idx_out2] + + # Find the position where we will insert the new point into the second window + idx_in2 = _bisect_left_kernel(sorted_cdf_values2, value_in2) + if value_in2 > value_out2: + idx_in2 -= 1 + + # Update the correspondence array + _insert_first_pop_last_kernel(&correspondence_indx2[0], idx_in2, nwin) + for j in range(1, nwin): + idx2 = correspondence_indx2[j] + correspondence_indx2[j] += _correspondence_indices_shift( + idx_in2, idx_out2, idx2) + + # Update the CDF window + _insert_pop_kernel(&sorted_cdf_values2[0], idx_in2, idx_out2, value_in2) + + iy2 += 1 + + # The array sorted_cdf_values2 is now centered on the correct point of y2 + # We have already calculated the rank-order of the point iy1, rank1 + # So we either directly map sorted_cdf_values2[rank1] to ynew, + # or alternatively we randomly draw a value between + # sorted_cdf_values2[rank1-1] and sorted_cdf_values2[rank1+1] + if add_subgrid_noise == 0: + y1_new[iy1] = sorted_cdf_values2[rank1] + else: + low_rank = rank1 - 1 + high_rank = rank1 + 1 + if low_rank < 0: + low_rank = 0 + elif high_rank >= nwin: + high_rank = nwin - 1 + low_cdf = sorted_cdf_values2[low_rank] + high_cdf = sorted_cdf_values2[high_rank] + y1_new[iy1] = low_cdf + (high_cdf-low_cdf)*random_uniform() + + # Move on to the next value in y1 + + # Find the value coming in and the value coming out + value_in1 = y1[iy1 + nhalfwin + 1] + idx_out1 = correspondence_indx1[nwin-1] + value_out1 = sorted_cdf_values1[idx_out1] + + # Find the position where we will insert the new point into the first window + idx_in1 = _bisect_left_kernel(sorted_cdf_values1, value_in1) + if value_in1 > value_out1: + idx_in1 -= 1 + + # Update the correspondence array + _insert_first_pop_last_kernel(&correspondence_indx1[0], idx_in1, nwin) + for i in range(1, nwin): + idx = correspondence_indx1[i] + correspondence_indx1[i] += _correspondence_indices_shift( + idx_in1, idx_out1, idx) + + # Update the CDF window + _insert_pop_kernel(&sorted_cdf_values1[0], idx_in1, idx_out1, value_in1) + + return y1_new diff --git a/halotools/empirical_models/abunmatch/engines/setup_package.py b/halotools/empirical_models/abunmatch/engines/setup_package.py new file mode 100644 index 000000000..029128f41 --- /dev/null +++ b/halotools/empirical_models/abunmatch/engines/setup_package.py @@ -0,0 +1,27 @@ +from distutils.extension import Extension +import os + +PATH_TO_PKG = os.path.relpath(os.path.dirname(__file__)) +SOURCES = ("bin_free_cam_kernel.pyx", ) +THIS_PKG_NAME = '.'.join(__name__.split('.')[:-1]) + + +def get_extensions(): + + names = [THIS_PKG_NAME + "." + src.replace('.pyx', '') for src in SOURCES] + sources = [os.path.join(PATH_TO_PKG, srcfn) for srcfn in SOURCES] + include_dirs = ['numpy'] + libraries = [] + language = 'c++' + extra_compile_args = ['-Ofast'] + + extensions = [] + for name, source in zip(names, sources): + extensions.append(Extension(name=name, + sources=[source], + include_dirs=include_dirs, + libraries=libraries, + language=language, + extra_compile_args=extra_compile_args)) + + return extensions diff --git a/halotools/empirical_models/abunmatch/tests/naive_python_cam.py b/halotools/empirical_models/abunmatch/tests/naive_python_cam.py new file mode 100644 index 000000000..4759e40b8 --- /dev/null +++ b/halotools/empirical_models/abunmatch/tests/naive_python_cam.py @@ -0,0 +1,43 @@ +""" Naive python implementation of bin-free conditional abundance matching +""" +import numpy as np + + +def sample2_window_indices(ix1, x_sample1, x_sample2, nwin): + """ For the point x1 = x_sample1[ix1], determine the indices of + the window surrounding each point in sample 2 that defines the + conditional probability distribution for `ynew`. + """ + nhalfwin = int(nwin/2) + npts2 = len(x_sample2) + + x1 = x_sample1[ix1] + iy2 = min(np.searchsorted(x_sample2, x1), npts2-1) + + if iy2 <= nhalfwin: + init_iy2_low, init_iy2_high = 0, nwin + elif iy2 >= npts2 - nhalfwin - 1: + init_iy2_low, init_iy2_high = npts2-nwin, npts2 + else: + init_iy2_low = iy2 - nhalfwin + init_iy2_high = init_iy2_low+nwin + + return init_iy2_low, init_iy2_high + + +def pure_python_rank_matching(x_sample1, ranks_sample1, + x_sample2, ranks_sample2, y_sample2, nwin): + """ Naive algorithm for implementing bin-free conditional abundance matching + for use in unit-testing. + """ + result = np.zeros_like(x_sample1) + + n1 = len(x_sample1) + + for i in range(n1): + low, high = sample2_window_indices(i, x_sample1, x_sample2, nwin) + sorted_window = np.sort(y_sample2[low:high]) + rank1 = ranks_sample1[i] + result[i] = sorted_window[rank1] + + return result diff --git a/halotools/empirical_models/abunmatch/tests/test_bin_free_cam.py b/halotools/empirical_models/abunmatch/tests/test_bin_free_cam.py new file mode 100644 index 000000000..0a5fd05a0 --- /dev/null +++ b/halotools/empirical_models/abunmatch/tests/test_bin_free_cam.py @@ -0,0 +1,505 @@ +""" +""" +import numpy as np +from astropy.utils.misc import NumpyRNGContext +from ..bin_free_cam import conditional_abunmatch +from ....utils.conditional_percentile import cython_sliding_rank, rank_order_function +from .naive_python_cam import pure_python_rank_matching +from ....utils import unsorting_indices + + +fixed_seed = 43 + + +def test1(): + """ Test case where x and x2 are sorted, y and y2 are sorted, + and the nearest x2 value is lined up with x + """ + nwin = 3 + + x = [1, 2, 3, 4, 5, 6, 7, 8, 9] + x2 = x + + y = np.arange(1, len(x)+1) + y2 = y*10. + + i2_matched = np.searchsorted(x2, x) + i2_matched = np.where(i2_matched >= len(y2), len(y2)-1, i2_matched) + + print("y = {0}".format(y)) + print("y2 = {0}\n".format(y2)) + + result = conditional_abunmatch(x, y, x2, y2, nwin, add_subgrid_noise=False) + print("ynew = {0}".format(result.astype('i4'))) + + assert np.all(result == y2) + + +def test2(): + """ Test case where x and x2 are sorted, y and y2 are not sorted, + and the nearest x2 value is lined up with x + """ + nwin = 3 + nhalfwin = int(nwin/2) + + x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9]) + x2 = x+0.01 + + with NumpyRNGContext(fixed_seed): + y = np.round(np.random.rand(len(x)), 2) + y2 = np.round(np.random.rand(len(x2)), 2) + + i2_matched = np.searchsorted(x2, x) + i2_matched = np.where(i2_matched >= len(y2), len(y2)-1, i2_matched) + + print("y = {0}".format(y)) + print("y2 = {0}\n".format(y2)) + + print("ranks1 = {0}".format(cython_sliding_rank(x, y, nwin))) + print("ranks2 = {0}".format(cython_sliding_rank(x2, y2, nwin))) + + result = conditional_abunmatch(x, y, x2, y2, nwin, add_subgrid_noise=False) + + print("\n\nynew = {0}".format(np.abs(result))) + print("y2 = {0}".format(y2)) + print("y = {0}\n".format(y)) + + # Test all points except edges + for itest in range(nhalfwin, len(x)-nhalfwin): + low = itest-nhalfwin + high = itest+nhalfwin+1 + window = y[low:high] + window2 = y2[low:high] + sorted_window2 = np.sort(window2) + window_ranks = rank_order_function(window) + itest_rank = window_ranks[nhalfwin] + itest_correct_result = sorted_window2[itest_rank] + itest_result = result[itest] + assert itest_result == itest_correct_result + + # Test left edge + for itest in range(nhalfwin): + low, high = 0, nwin + window = y[low:high] + window2 = y2[low:high] + sorted_window2 = np.sort(window2) + window_ranks = rank_order_function(window) + itest_rank = window_ranks[itest] + itest_correct_result = sorted_window2[itest_rank] + itest_result = result[itest] + msg = "itest_result = {0}, correct result = {1}" + assert itest_result == itest_correct_result, msg.format( + itest_result, itest_correct_result) + + # Test right edge + for iwin in range(nhalfwin+1, nwin): + itest = iwin + len(x) - nwin + low, high = len(x)-nwin, len(x) + window = y[low:high] + window2 = y2[low:high] + sorted_window2 = np.sort(window2) + window_ranks = rank_order_function(window) + itest_rank = window_ranks[iwin] + itest_correct_result = sorted_window2[itest_rank] + itest_result = result[itest] + msg = "itest_result = {0}, correct result = {1}" + assert itest_result == itest_correct_result, msg.format( + itest_result, itest_correct_result) + + +def test3(): + """ Test hard-coded case where x and x2 are sorted, y and y2 are sorted, + but the nearest x--x2 correspondence is no longer simple + """ + nwin = 3 + + x = np.array([0.1, 0.36, 0.36, 0.74, 0.83]) + x2 = np.array([0.54, 0.54, 0.55, 0.56, 0.57]) + + y = np.array([0.12, 0.13, 0.24, 0.33, 0.61]) + y2 = np.array([0.03, 0.54, 0.67, 0.73, 0.86]) + + i2_matched = np.searchsorted(x2, x) + i2_matched = np.where(i2_matched >= len(y2), len(y2)-1, i2_matched) + i2_matched = np.array([0, 0, 0, 4, 4]) + + result = conditional_abunmatch(x, y, x2, y2, nwin, add_subgrid_noise=False) + correct_result = [0.03, 0.54, 0.54, 0.73, 0.86] + + assert np.allclose(result, correct_result) + + +def test4(): + """ Regression test for buggy treatment of rightmost endpoint behavior + """ + + n1, n2, nwin = 8, 8, 3 + x = np.round(np.linspace(0.15, 1.3, n1), 2) + with NumpyRNGContext(fixed_seed): + y = np.round(np.random.uniform(0, 1, n1), 2) + ranks_sample1 = cython_sliding_rank(x, y, nwin) + + x2 = np.round(np.linspace(0.15, 1.3, n2), 2) + with NumpyRNGContext(fixed_seed): + y2 = np.round(np.random.uniform(-4, -3, n2), 2) + ranks_sample2 = cython_sliding_rank(x2, y2, nwin) + + pure_python_result = pure_python_rank_matching(x, ranks_sample1, + x2, ranks_sample2, y2, nwin) + + result = conditional_abunmatch(x, y, x2, y2, nwin, add_subgrid_noise=False) + + assert np.allclose(result, pure_python_result) + + +def test_brute_force_interior_points(): + """ + """ + num_tests = 50 + + nwin = 11 + nhalfwin = nwin/2 + + for i in range(num_tests): + seed = fixed_seed + i + with NumpyRNGContext(seed): + x1_low, x2_low = np.random.uniform(-10, 10, 2) + x1_high, x2_high = np.random.uniform(100, 200, 2) + n1, n2 = np.random.randint(30, 100, 2) + x = np.sort(np.random.uniform(x1_low, x1_high, n1)) + x2 = np.sort(np.random.uniform(x2_low, x2_high, n2)) + + y1_low, y2_low = np.random.uniform(-10, 10, 2) + y1_high, y2_high = np.random.uniform(100, 200, 2) + y = np.random.uniform(y1_low, y1_high, n1) + y2 = np.random.uniform(y2_low, y2_high, n2) + + ranks_sample1 = cython_sliding_rank(x, y, nwin) + ranks_sample2 = cython_sliding_rank(x2, y2, nwin) + + pure_python_result = pure_python_rank_matching(x, ranks_sample1, + x2, ranks_sample2, y2, nwin) + + cython_result = conditional_abunmatch(x, y, x2, y2, nwin, add_subgrid_noise=False) + + assert np.allclose(pure_python_result[nhalfwin:-nhalfwin], + cython_result[nhalfwin:-nhalfwin]) + + +def test_brute_force_left_endpoints(): + """ + """ + + num_tests = 50 + + nwin = 11 + nhalfwin = nwin/2 + + for i in range(num_tests): + seed = fixed_seed + i + with NumpyRNGContext(seed): + x1_low, x2_low = np.random.uniform(-10, 10, 2) + x1_high, x2_high = np.random.uniform(100, 200, 2) + n1, n2 = np.random.randint(30, 100, 2) + x = np.sort(np.random.uniform(x1_low, x1_high, n1)) + x2 = np.sort(np.random.uniform(x2_low, x2_high, n2)) + + y1_low, y2_low = np.random.uniform(-10, 10, 2) + y1_high, y2_high = np.random.uniform(100, 200, 2) + y = np.random.uniform(y1_low, y1_high, n1) + y2 = np.random.uniform(y2_low, y2_high, n2) + + ranks_sample1 = cython_sliding_rank(x, y, nwin) + ranks_sample2 = cython_sliding_rank(x2, y2, nwin) + + pure_python_result = pure_python_rank_matching(x, ranks_sample1, + x2, ranks_sample2, y2, nwin) + + cython_result = conditional_abunmatch(x, y, x2, y2, nwin, add_subgrid_noise=False) + + # Test left edge + assert np.allclose(pure_python_result[:nhalfwin], cython_result[:nhalfwin]) + + +def test_brute_force_right_points(): + """ + """ + + num_tests = 50 + + nwin = 11 + nhalfwin = nwin/2 + + for i in range(num_tests): + seed = fixed_seed + i + with NumpyRNGContext(seed): + x1_low, x2_low = np.random.uniform(-10, 10, 2) + x1_high, x2_high = np.random.uniform(100, 200, 2) + n1, n2 = np.random.randint(30, 100, 2) + x = np.sort(np.random.uniform(x1_low, x1_high, n1)) + x2 = np.sort(np.random.uniform(x2_low, x2_high, n2)) + + y1_low, y2_low = np.random.uniform(-10, 10, 2) + y1_high, y2_high = np.random.uniform(100, 200, 2) + y = np.random.uniform(y1_low, y1_high, n1) + y2 = np.random.uniform(y2_low, y2_high, n2) + + ranks_sample1 = cython_sliding_rank(x, y, nwin) + ranks_sample2 = cython_sliding_rank(x2, y2, nwin) + + pure_python_result = pure_python_rank_matching(x, ranks_sample1, + x2, ranks_sample2, y2, nwin) + + cython_result = conditional_abunmatch(x, y, x2, y2, nwin, add_subgrid_noise=False) + + # Test right edge + assert np.allclose(pure_python_result[-nhalfwin:], cython_result[-nhalfwin:]) + + +def test_hard_coded_case1(): + nwin = 3 + + x = np.array([0.1, 0.36, 0.5, 0.74, 0.83]) + x2 = np.copy(x) + + y = np.array([0.12, 0.13, 0.24, 0.33, 0.61]) + y2 = np.array([0.03, 0.54, 0.67, 0.73, 0.86]) + + correct_result = [0.03, 0.54, 0.67, 0.73, 0.86] + result = conditional_abunmatch(x, y, x2, y2, nwin, add_subgrid_noise=False) + + assert np.allclose(result, correct_result) + +def test_hard_coded_case2(): + nwin = 3 + + x = np.array([0.1, 0.36, 0.36, 0.74, 0.83]) + x2 = np.array([0.54, 0.54, 0.55, 0.56, 0.57]) + + y = np.array([0.12, 0.13, 0.24, 0.33, 0.61]) + y2 = np.array([0.03, 0.54, 0.67, 0.73, 0.86]) + + correct_result = [0.03, 0.54, 0.54, 0.73, 0.86] + result = conditional_abunmatch(x, y, x2, y2, nwin, add_subgrid_noise=False) + + assert np.allclose(result, correct_result) + + +def test_hard_coded_case3(): + """ x==x2. + + So the CAM windows are always the same. + So the first two windows are the leftmost edge, + the middle entry uses the middle window, + and the last two entries use the rightmost edge window. + """ + nwin = 3 + + x = np.array([0.1, 0.36, 0.5, 0.74, 0.83]) + x2 = np.copy(x) + + y = np.array([0.12, 0.13, 0.24, 0.33, 0.61]) + y2 = np.array([0.3, 0.04, 0.6, 10., 5.]) + + correct_result = [0.04, 0.3, 0.6, 5., 10.] + result = conditional_abunmatch(x, y, x2, y2, nwin, add_subgrid_noise=False) + + assert np.allclose(result, correct_result) + + +def test_hard_coded_case5(): + nwin = 3 + + x = np.array((1., 1., 1, 1, 1)) + x2 = np.array([0.1, 0.36, 0.5, 0.74, 0.83]) + + y = np.array([0.12, 0.13, 0.24, 0.33, 0.61]) + y2 = np.array([0.3, 0.04, 0.6, 10., 5.]) + + correct_result = [0.6, 5., 5., 5., 10.] + result = conditional_abunmatch(x, y, x2, y2, nwin, add_subgrid_noise=False) + + print("\n\ncorrect result = {0}".format(correct_result)) + print("cython result = {0}\n".format(result)) + + msg = "Cython implementation incorrectly ignores searchsorted result for edges" + assert np.allclose(result, correct_result), msg + + +def test_hard_coded_case4(): + """ Every x2 is larger than the largest x. + + So the only CAM window ever used is the first 3 elements of y2. + """ + nwin = 3 + + x = np.array((0., 0., 0., 0., 0.)) + x2 = np.array([0.1, 0.36, 0.5, 0.74, 0.83]) + + y = np.array([0.12, 0.13, 0.24, 0.33, 0.61]) + y2 = np.array([0.3, 0.04, 0.6, 10., 5.]) + + correct_result = [0.04, 0.3, 0.3, 0.3, 0.6] + result = conditional_abunmatch(x, y, x2, y2, nwin, add_subgrid_noise=False) + + assert np.allclose(result, correct_result) + + +def test_hard_coded_case6(): + """ + """ + + x = [0.15, 0.31, 0.48, 0.64, 0.81, 0.97, 1.14, 1.3] + x2 = [0.15, 0.38, 0.61, 0.84, 1.07, 1.3] + + y = [0.22, 0.87, 0.21, 0.92, 0.49, 0.61, 0.77, 0.52] + y2 = [-3.78, -3.13, -3.79, -3.08, -3.51, -3.39] + + nwin = 5 + + ranks_sample1 = cython_sliding_rank(x, y, nwin) + ranks_sample2 = cython_sliding_rank(x2, y2, nwin) + + pure_python_result = pure_python_rank_matching(x, ranks_sample1, + x2, ranks_sample2, y2, nwin) + + result = conditional_abunmatch(x, y, x2, y2, nwin, add_subgrid_noise=False) + + assert np.allclose(result, pure_python_result) + + +def test_subgrid_noise1(): + n1, n2 = int(5e4), int(5e3) + + with NumpyRNGContext(fixed_seed): + x = np.sort(np.random.uniform(0, 10, n1)) + y = np.random.uniform(0, 1, n1) + + with NumpyRNGContext(fixed_seed): + x2 = np.sort(np.random.uniform(0, 10, n2)) + y2 = np.random.uniform(-4, -3, n2) + + nwin1 = 201 + result = conditional_abunmatch(x, y, x2, y2, nwin1, add_subgrid_noise=False) + result2 = conditional_abunmatch(x, y, x2, y2, nwin1, add_subgrid_noise=True) + assert np.allclose(result, result2, atol=0.1) + assert not np.allclose(result, result2, atol=0.02) + + nwin2 = 1001 + result = conditional_abunmatch(x, y, x2, y2, nwin2, add_subgrid_noise=False) + result2 = conditional_abunmatch(x, y, x2, y2, nwin2, add_subgrid_noise=True) + assert np.allclose(result, result2, atol=0.02) + + +def test_initial_sorting1(): + """ + """ + n1, n2 = int(2e3), int(1e3) + + with NumpyRNGContext(fixed_seed): + x = np.sort(np.random.uniform(0, 10, n1)) + y = np.random.uniform(0, 1, n1) + + with NumpyRNGContext(fixed_seed): + x2 = np.sort(np.random.uniform(0, 10, n2)) + y2 = np.random.uniform(-4, -3, n2) + + nwin1 = 101 + result = conditional_abunmatch( + x, y, x2, y2, nwin1, assume_x_is_sorted=False, assume_x2_is_sorted=False, + add_subgrid_noise=False) + result2 = conditional_abunmatch( + x, y, x2, y2, nwin1, assume_x_is_sorted=True, assume_x2_is_sorted=True, + add_subgrid_noise=False) + assert np.allclose(result, result2) + + + +def test_initial_sorting2(): + """ + """ + n1, n2 = int(2e3), int(1e3) + + with NumpyRNGContext(fixed_seed): + x = np.sort(np.random.uniform(0, 10, n1)) + y = np.random.uniform(0, 1, n1) + + with NumpyRNGContext(fixed_seed): + x2 = np.random.uniform(0, 10, n2) + y2 = np.random.uniform(-4, -3, n2) + + nwin1 = 101 + result = conditional_abunmatch( + x, y, x2, y2, nwin1, assume_x_is_sorted=False, assume_x2_is_sorted=False, + add_subgrid_noise=False) + result2 = conditional_abunmatch( + x, y, x2, y2, nwin1, assume_x_is_sorted=True, assume_x2_is_sorted=False, + add_subgrid_noise=False) + assert np.allclose(result, result2) + + +def test_initial_sorting3(): + """ + """ + n1, n2 = int(2e3), int(1e3) + + with NumpyRNGContext(fixed_seed): + x = np.random.uniform(0, 10, n1) + y = np.random.uniform(0, 1, n1) + + with NumpyRNGContext(fixed_seed): + x2 = np.sort(np.random.uniform(0, 10, n2)) + y2 = np.random.uniform(-4, -3, n2) + + nwin1 = 101 + result = conditional_abunmatch( + x, y, x2, y2, nwin1, assume_x_is_sorted=False, assume_x2_is_sorted=True, + add_subgrid_noise=False) + result2 = conditional_abunmatch( + x, y, x2, y2, nwin1, assume_x_is_sorted=False, assume_x2_is_sorted=False, + add_subgrid_noise=False) + assert np.allclose(result, result2) + + +def test_initial_sorting4(): + """ + """ + n1, n2 = int(2e3), int(1e3) + + with NumpyRNGContext(fixed_seed): + x = np.random.uniform(0, 10, n1) + y = np.random.uniform(0, 1, n1) + + with NumpyRNGContext(fixed_seed): + x2 = np.random.uniform(0, 10, n2) + y2 = np.random.uniform(-4, -3, n2) + + nwin1 = 101 + result = conditional_abunmatch( + x, y, x2, y2, nwin1, + assume_x_is_sorted=False, assume_x2_is_sorted=False, + add_subgrid_noise=False) + + idx_x_sorted = np.argsort(x) + x_sorted = x[idx_x_sorted] + y_sorted = y[idx_x_sorted] + result2 = conditional_abunmatch( + x_sorted, y_sorted, x2, y2, nwin1, + assume_x_is_sorted=True, assume_x2_is_sorted=False, + add_subgrid_noise=False) + assert np.allclose(result, result2[unsorting_indices(idx_x_sorted)]) + + idx_x2_sorted = np.argsort(x2) + x2_sorted = x2[idx_x2_sorted] + y2_sorted = y2[idx_x2_sorted] + result3 = conditional_abunmatch( + x, y, x2_sorted, y2_sorted, nwin1, + assume_x_is_sorted=False, assume_x2_is_sorted=True, + add_subgrid_noise=False) + assert np.allclose(result, result3) + + result4 = conditional_abunmatch( + x_sorted, y_sorted, x2_sorted, y2_sorted, nwin1, + assume_x_is_sorted=True, assume_x2_is_sorted=True, + add_subgrid_noise=False) + assert np.allclose(result, result4[unsorting_indices(idx_x_sorted)]) diff --git a/halotools/empirical_models/abunmatch/tests/test_conditional_abunmatch.py b/halotools/empirical_models/abunmatch/tests/test_conditional_abunmatch.py index 994870274..9db239be3 100644 --- a/halotools/empirical_models/abunmatch/tests/test_conditional_abunmatch.py +++ b/halotools/empirical_models/abunmatch/tests/test_conditional_abunmatch.py @@ -10,7 +10,7 @@ def test_conditional_abunmatch_bin_based1(): with NumpyRNGContext(43): x = np.random.normal(loc=0, scale=0.1, size=100) y = np.linspace(10, 20, 100) - model_y = conditional_abunmatch_bin_based(x, y, seed=43) + model_y = conditional_abunmatch_bin_based(x, y, seed=43, npts_lookup_table=len(y)) msg = "monotonic cam does not preserve mean" assert np.allclose(model_y.mean(), y.mean(), rtol=0.1), msg @@ -19,7 +19,7 @@ def test_conditional_abunmatch_bin_based2(): with NumpyRNGContext(43): x = np.random.normal(loc=0, scale=0.1, size=100) y = np.linspace(10, 20, 100) - model_y = conditional_abunmatch_bin_based(x, y, seed=43) + model_y = conditional_abunmatch_bin_based(x, y, seed=43, npts_lookup_table=len(y)) idx_x_sorted = np.argsort(x) msg = "monotonic cam does not preserve correlation" high = model_y[idx_x_sorted][-50:].mean() @@ -33,7 +33,7 @@ def test_conditional_abunmatch_bin_based3(): with NumpyRNGContext(43): x = np.random.normal(loc=0, scale=0.1, size=100) y = np.linspace(10, 20, 100) - model_y = conditional_abunmatch_bin_based(x, y, sigma=0.01, seed=43) + model_y = conditional_abunmatch_bin_based(x, y, sigma=0.01, seed=43, npts_lookup_table=len(y)) idx_x_sorted = np.argsort(x) msg = "low-noise cam does not preserve correlation" high = model_y[idx_x_sorted][-50:].mean() diff --git a/halotools/empirical_models/abunmatch/tests/test_pure_python.py b/halotools/empirical_models/abunmatch/tests/test_pure_python.py new file mode 100644 index 000000000..34e43a8e5 --- /dev/null +++ b/halotools/empirical_models/abunmatch/tests/test_pure_python.py @@ -0,0 +1,156 @@ +""" +""" +import numpy as np +from astropy.utils.misc import NumpyRNGContext +from ....utils.conditional_percentile import cython_sliding_rank +from .naive_python_cam import sample2_window_indices, pure_python_rank_matching + +fixed_seed = 43 + + +def test_pure_python1(): + """ + """ + n1, n2, nwin = 5001, 1001, 11 + nhalfwin = nwin/2 + x_sample1 = np.linspace(0, 1, n1) + with NumpyRNGContext(fixed_seed): + y_sample1 = np.random.uniform(0, 1, n1) + ranks_sample1 = cython_sliding_rank(x_sample1, y_sample1, nwin) + + x_sample2 = np.linspace(0, 1, n2) + with NumpyRNGContext(fixed_seed): + y_sample2 = np.random.uniform(-4, -3, n2) + ranks_sample2 = cython_sliding_rank(x_sample2, y_sample2, nwin) + + result = pure_python_rank_matching(x_sample1, ranks_sample1, + x_sample2, ranks_sample2, y_sample2, nwin) + + for ix1 in range(2*nwin, n1-2*nwin): + + rank1 = ranks_sample1[ix1] + low, high = sample2_window_indices(ix1, x_sample1, x_sample2, nwin) + + sorted_window2 = np.sort(y_sample2[low:high]) + assert len(sorted_window2) == nwin + + correct_result_ix1 = sorted_window2[rank1] + + assert correct_result_ix1 == result[ix1] + + +def test_hard_coded_case1(): + nwin = 3 + + x = np.array([0.1, 0.36, 0.5, 0.74, 0.83]) + x2 = np.copy(x) + + y = np.array([0.12, 0.13, 0.24, 0.33, 0.61]) + y2 = np.array([0.03, 0.54, 0.67, 0.73, 0.86]) + + ranks_sample1 = cython_sliding_rank(x, y, nwin) + ranks_sample2 = cython_sliding_rank(x2, y2, nwin) + + pure_python_result = pure_python_rank_matching(x, ranks_sample1, + x2, ranks_sample2, y2, nwin) + + correct_result = [0.03, 0.54, 0.67, 0.73, 0.86] + + assert np.allclose(pure_python_result, correct_result) + + +def test_hard_coded_case2(): + """ + """ + nwin = 3 + + x = np.array([0.1, 0.36, 0.36, 0.74, 0.83]) + x2 = np.array([0.54, 0.54, 0.55, 0.56, 0.57]) + + y = np.array([0.12, 0.13, 0.24, 0.33, 0.61]) + y2 = np.array([0.03, 0.54, 0.67, 0.73, 0.86]) + + ranks_sample1 = cython_sliding_rank(x, y, nwin) + ranks_sample2 = cython_sliding_rank(x2, y2, nwin) + + pure_python_result = pure_python_rank_matching(x, ranks_sample1, + x2, ranks_sample2, y2, nwin) + + correct_result = [0.03, 0.54, 0.54, 0.73, 0.86] + + assert np.allclose(pure_python_result, correct_result) + + +def test_hard_coded_case3(): + """ x==x2. + + So the CAM windows are always the same. + So the first two windows are the leftmost edge, + the middle entry uses the middle window, + and the last two entries use the rightmost edge window. + """ + nwin = 3 + + x = np.array([0.1, 0.36, 0.5, 0.74, 0.83]) + x2 = np.copy(x) + + y = np.array([0.12, 0.13, 0.24, 0.33, 0.61]) + y2 = np.array([0.3, 0.04, 0.6, 10., 5.]) + + ranks_sample1 = cython_sliding_rank(x, y, nwin) + ranks_sample2 = cython_sliding_rank(x2, y2, nwin) + + pure_python_result = pure_python_rank_matching(x, ranks_sample1, + x2, ranks_sample2, y2, nwin) + + correct_result = [0.04, 0.3, 0.6, 5., 10.] + + assert np.allclose(pure_python_result, correct_result) + + +def test_hard_coded_case4(): + """ Every x2 is larger than the largest x. + + So the only CAM window ever used is the first 3 elements of y2. + """ + nwin = 3 + + x = np.array((0., 0., 0., 0., 0.)) + x2 = np.array([0.1, 0.36, 0.5, 0.74, 0.83]) + + y = np.array([0.12, 0.13, 0.24, 0.33, 0.61]) + y2 = np.array([0.3, 0.04, 0.6, 10., 5.]) + + ranks_sample1 = cython_sliding_rank(x, y, nwin) + ranks_sample2 = cython_sliding_rank(x2, y2, nwin) + + pure_python_result = pure_python_rank_matching(x, ranks_sample1, + x2, ranks_sample2, y2, nwin) + + correct_result = [0.04, 0.3, 0.3, 0.3, 0.6] + + assert np.allclose(pure_python_result, correct_result) + + +def test_hard_coded_case5(): + """ Every x2 is smaller than the smallest x. + + So the only CAM window ever used is the final 3 elements of y2. + """ + nwin = 3 + + x = np.array((1., 1., 1, 1, 1)) + x2 = np.array([0.1, 0.36, 0.5, 0.74, 0.83]) + + y = np.array([0.12, 0.13, 0.24, 0.33, 0.61]) + y2 = np.array([0.3, 0.04, 0.6, 10., 5.]) + + ranks_sample1 = cython_sliding_rank(x, y, nwin) + ranks_sample2 = cython_sliding_rank(x2, y2, nwin) + + pure_python_result = pure_python_rank_matching(x, ranks_sample1, + x2, ranks_sample2, y2, nwin) + + correct_result = [0.6, 5, 5, 5, 10] + + assert np.allclose(pure_python_result, correct_result) diff --git a/halotools/empirical_models/abunmatch/tests/test_sample2_window_function.py b/halotools/empirical_models/abunmatch/tests/test_sample2_window_function.py new file mode 100644 index 000000000..b9a23bb7e --- /dev/null +++ b/halotools/empirical_models/abunmatch/tests/test_sample2_window_function.py @@ -0,0 +1,121 @@ +""" Module testing the sample2_window_indices function that returns the +relevant CAM window to the naive python implementation. +""" +import numpy as np +from .naive_python_cam import sample2_window_indices + + +def test_left_edge_window(): + """ Setup: x1 == x2. Enforce proper behavior at the leftmost edge. + """ + n1, n2 = 20, 20 + x_sample1 = np.arange(n1) + x_sample2 = np.arange(n2) + + nwin = 5 + + ix1 = 0 + init_iy2_low, init_iy2_high = sample2_window_indices( + ix1, x_sample1, x_sample2, nwin) + assert (init_iy2_low, init_iy2_high) == (0, nwin) + assert len(x_sample2[init_iy2_low:init_iy2_high]) == nwin + + ix1 = 1 + init_iy2_low, init_iy2_high = sample2_window_indices( + ix1, x_sample1, x_sample2, nwin) + assert (init_iy2_low, init_iy2_high) == (0, nwin) + assert len(x_sample2[init_iy2_low:init_iy2_high]) == nwin + + ix1 = 2 + init_iy2_low, init_iy2_high = sample2_window_indices( + ix1, x_sample1, x_sample2, nwin) + assert (init_iy2_low, init_iy2_high) == (0, nwin) + assert len(x_sample2[init_iy2_low:init_iy2_high]) == nwin + + ix1 = 3 + init_iy2_low, init_iy2_high = sample2_window_indices( + ix1, x_sample1, x_sample2, nwin) + assert (init_iy2_low, init_iy2_high) == (1, nwin+1) + assert len(x_sample2[init_iy2_low:init_iy2_high]) == nwin + + ix1 = 4 + init_iy2_low, init_iy2_high = sample2_window_indices( + ix1, x_sample1, x_sample2, nwin) + assert (init_iy2_low, init_iy2_high) == (2, nwin+2) + assert len(x_sample2[init_iy2_low:init_iy2_high]) == nwin + + +def test_right_edge_window(): + """ Setup: x1 == x2. Enforce proper behavior at the rightmost edge. + """ + n1, n2 = 20, 20 + x_sample1 = np.arange(n1) + x_sample2 = np.arange(n2) + + nwin = 5 + + ix1 = 19 + init_iy2_low, init_iy2_high = sample2_window_indices( + ix1, x_sample1, x_sample2, nwin) + assert (init_iy2_low, init_iy2_high) == (n2-nwin, n2) + assert len(x_sample2[init_iy2_low:init_iy2_high]) == nwin + + ix1 = 18 + init_iy2_low, init_iy2_high = sample2_window_indices( + ix1, x_sample1, x_sample2, nwin) + assert (init_iy2_low, init_iy2_high) == (n2-nwin, n2) + assert len(x_sample2[init_iy2_low:init_iy2_high]) == nwin + + ix1 = 17 + init_iy2_low, init_iy2_high = sample2_window_indices( + ix1, x_sample1, x_sample2, nwin) + assert (init_iy2_low, init_iy2_high) == (n2-nwin, n2) + assert len(x_sample2[init_iy2_low:init_iy2_high]) == nwin + + ix1 = 16 + init_iy2_low, init_iy2_high = sample2_window_indices( + ix1, x_sample1, x_sample2, nwin) + assert (init_iy2_low, init_iy2_high) == (n2-nwin-1, n2-1) + assert len(x_sample2[init_iy2_low:init_iy2_high]) == nwin + + ix1 = 15 + init_iy2_low, init_iy2_high = sample2_window_indices( + ix1, x_sample1, x_sample2, nwin) + assert (init_iy2_low, init_iy2_high) == (n2-nwin-2, n2-2) + assert len(x_sample2[init_iy2_low:init_iy2_high]) == nwin + + +def test_all_x1_less_than_x2(): + """ Setup: np.all(x1 < x2.min()). + + Enforce proper behavior at the leftmost edge. + """ + n1, n2 = 20, 20 + x_sample1 = np.arange(n1) + x_sample2 = np.arange(100, 100+n2) + + nwin = 5 + + for ix1 in range(n1): + init_iy2_low, init_iy2_high = sample2_window_indices( + ix1, x_sample1, x_sample2, nwin) + assert (init_iy2_low, init_iy2_high) == (0, nwin), "ix1 = {0}".format(ix1) + assert len(x_sample2[init_iy2_low:init_iy2_high]) == nwin + + +def test_all_x1_greater_than_x2(): + """ Setup: np.all(x1 < x2.min()). + + Enforce proper behavior at the leftmost edge. + """ + n1, n2 = 20, 20 + x_sample1 = np.arange(n1) + x_sample2 = np.arange(-100, -100+n2) + + nwin = 5 + + for ix1 in range(n1): + init_iy2_low, init_iy2_high = sample2_window_indices( + ix1, x_sample1, x_sample2, nwin) + assert (init_iy2_low, init_iy2_high) == (n2-nwin, n2), "ix1 = {0}".format(ix1) + assert len(x_sample2[init_iy2_low:init_iy2_high]) == nwin diff --git a/halotools/empirical_models/abunmatch/tests/test_single_unit.py b/halotools/empirical_models/abunmatch/tests/test_single_unit.py new file mode 100644 index 000000000..89f385d7d --- /dev/null +++ b/halotools/empirical_models/abunmatch/tests/test_single_unit.py @@ -0,0 +1,12 @@ +""" +""" +import numpy as np +from astropy.utils.misc import NumpyRNGContext +from ..bin_free_cam import conditional_abunmatch + + +fixed_seed = 5 + + +def test(): + pass From a5fcb525f651aad28964f7a57bdbe4e6db676f37 Mon Sep 17 00:00:00 2001 From: Andrew Hearin Date: Mon, 12 Mar 2018 15:28:26 -0600 Subject: [PATCH 2/5] Updated documentation --- docs/_static/cam_example_assembias_clf.png | Bin 0 -> 44595 bytes docs/_static/cam_example_bulge_disk_ratio.png | Bin 0 -> 12849 bytes docs/_static/cam_example_complex_sfr.png | Bin 0 -> 15023 bytes ...m_example_complex_sfr_dmdt_correlation.png | Bin 0 -> 15931 bytes .../cam_example_complex_sfr_recovery.png | Bin 0 -> 12004 bytes docs/function_usage/utility_functions.rst | 7 + .../cam_complex_sfr_tutorial.ipynb | 362 ++++++++++++++++++ .../cam_modeling/cam_decorated_clf.ipynb | 223 +++++++++++ .../cam_disk_bulge_ratios_demo.ipynb | 252 ++++++++++++ docs/quickstart_and_tutorials/index.rst | 2 +- .../cam_tutorial_pages/cam_complex_sfr.rst | 95 +++++ .../cam_tutorial_pages/cam_decorated_clf.rst | 92 +++++ .../cam_disk_bulge_ratios.rst | 90 +++++ .../cam_quenching_gradients.rst | 48 +++ .../cam_tutorial_pages/cam_tutorial.rst | 151 ++++++++ .../abundance_matching_composite_model.rst | 5 +- docs/whats_new_history/whats_new_0.7.rst | 7 + 17 files changed, 1332 insertions(+), 2 deletions(-) create mode 100644 docs/_static/cam_example_assembias_clf.png create mode 100644 docs/_static/cam_example_bulge_disk_ratio.png create mode 100644 docs/_static/cam_example_complex_sfr.png create mode 100644 docs/_static/cam_example_complex_sfr_dmdt_correlation.png create mode 100644 docs/_static/cam_example_complex_sfr_recovery.png create mode 100644 docs/notebooks/cam_modeling/cam_complex_sfr_tutorial.ipynb create mode 100644 docs/notebooks/cam_modeling/cam_decorated_clf.ipynb create mode 100644 docs/notebooks/cam_modeling/cam_disk_bulge_ratios_demo.ipynb create mode 100644 docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial_pages/cam_complex_sfr.rst create mode 100644 docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial_pages/cam_decorated_clf.rst create mode 100644 docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial_pages/cam_disk_bulge_ratios.rst create mode 100644 docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial_pages/cam_quenching_gradients.rst create mode 100644 docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial_pages/cam_tutorial.rst diff --git a/docs/_static/cam_example_assembias_clf.png b/docs/_static/cam_example_assembias_clf.png new file mode 100644 index 0000000000000000000000000000000000000000..980609e26555b412ae9eae9c11d56df25db789a6 GIT binary patch literal 44595 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 V$E|a6uCXZir>kYCnXh3L@IL?<+Xest literal 0 HcmV?d00001 diff --git a/docs/_static/cam_example_bulge_disk_ratio.png b/docs/_static/cam_example_bulge_disk_ratio.png new file mode 100644 index 0000000000000000000000000000000000000000..dc64d8a421e869313f983e6e42d4acf5895d88dd GIT binary patch literal 12849 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 literal 0 HcmV?d00001 diff --git a/docs/_static/cam_example_complex_sfr.png b/docs/_static/cam_example_complex_sfr.png new file mode 100644 index 0000000000000000000000000000000000000000..d187d358744007abbee2dca71b897fd43f4c77c0 GIT binary patch literal 15023 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 NK}JQoO3F0!{{m}&i39)u literal 0 HcmV?d00001 diff --git a/docs/_static/cam_example_complex_sfr_dmdt_correlation.png b/docs/_static/cam_example_complex_sfr_dmdt_correlation.png new file mode 100644 index 0000000000000000000000000000000000000000..9249af4de25985c0a020e00ed05c6a6e0a88d870 GIT binary patch literal 15931 zcmZ9z1wfSH)-61AcS_oUIUA*L=XBBVSdVFQwB$@B#D?&%OU$-(MI_81*qH)P=DP~7> zg`G{6l!xm zqvWKd5OSi!W$$%!qJNeySeAMjij6BS9U3=iE^cN<^VdfN0yX9otI(AAHZ`cv4bIkG z=pO;LH)+uA=!SzRDk@fMvvQ|=0v9D%6~^|atk<6OC86NT z3FYrB`IF@d{yJyx^*$OZDq?_5a8+r+A#n=*M`X%pV9&5fYi9A7f{>|0f6`d=M(o{I z^BYxER8;F$1azT6(0gecet{$9Cnb(J@MU0lw22(K5{I9AFmsZ`80!#%kAj>=WGXZn z+B3msn*~4n`nP{szTzQKoUakRyu9T3`2{;s#nqL95kE*gFW#+dZ`sVsKUsp~P3#@t z^!h4kN?tRS*5n+rRGI4s`B^{HA`Rvm$2C!_2Hi7ZtQ3pVa$aglLXn380jDxbB<0I3 z3*_A*}_-N~f zinQ0e3oQ-Ebd5vuH&hM46!mw-==55enXhJUJe;8Xi?Q*cDzhhN^>the@`t0cN!a@x z#bM{Q9|Yr-n$pYHMH?srN^KJ}TR%2lIkd064lP{}Z2z0p<~;J=0%k1rU^xwE0}l(> zlB_U&@dxLUCA!d&!2q&0&QR^KeEy0OD3POH%-Cw}OFP$vjVXuwqhxij+nb{1q1NJB z_?(q$*lWj<;MP`ru(FxW0-r0V9H^a?+MH$ygjt#Va6^H`!a~f{U;W-1*r3P=2lsP7 zkFQ@ab#blk6sOoL!bi6({f$#Vl)UB&r{Yh(u&_!Ku2K83ja8XKM;xW_OkAMvE&l2{ zVxc!R#5Of$b8{U&oZ&;hPe6Vz15M>Eusoh{`r`9|Q|#44@;+bx5U(mv?3IUt#4x#| zowV*ivZMdJjt}JhOdsBRoybzgW+n%G-Yiw1Nj!DsV)R3%ei22P(KE67wtepPHb(lZ z%9^hkKNEK_9KYvLdKYktP$!z7>#K@3Xfom4vNz0u~l>E<@{eO?h3`GM_8hiJupZe(DjTu@$G5;Fw z{`2YOG-rR$$n}R}V%~Q+qpd&udwzsH;-F@@Ndvc*D>2)P7@u&*?0P*J9XwH*JC>H2 zCNTyj_CfrSs4w)A2t2jO?d1aXo0SAZH!{(P>$~u6c1*dq&(T8e*Qj^(Q|S2j#?yJa zcO#>N2DFJi&$mn6BJ+)tlNr6yEk-FnQ1V{K&kq-lX4Ci=Q`ezDXsE=7gltNBT2}9E zH_G4ht=~HX6NlMdIZrK)PGopf8oty~Z^0IX#AF#i4!^N)U1HsJU&iImiNr?tCVfq| zwR$t?15R&F6*mx4&=E2pxXEq!aK;^*xx-{7E1!2pO)JqX{q5}E;P8L`ba&?R(_tjE z5Ek6ZKpLgFg2i^azvuk%?ZXZ`NrI|FEUaz0LQQ~_po6YzFsYWt zdic(cwi@xhTgOMHAE?CwzZ>@#1T*&-oJK{$(IoJ+bv5C^O|Vume)47)DFrb-5k6#V z>Lyni`Nc*6^5`&4B7YioZapU!u#7)0F7X)!VoI^FEvOrX*#+1RXp|hkhNYa}vdg3H zo?cv3-XHnvdwr+kN7|aIBhEtrTN-izwQ=B-}78Y80ng-teYYgWpHa z8x<5p&BFyQL`((Sd(74H?_u3%aHlodfcI-OSLuE%+L3Z0ijxfPiHMi_<0UNMZ4Dm* zcFe@eya>T_A!4Z@*xSo*{f@0Lj9OFQ4t{*9R?|TTYcQ>uY?@q4?A;>hLX^Y`3(m@w zo^`R_vHJGe>GW0{$V87n`r7H9m==Xi_6Y00H;gPFimv|Njyz)#CjRpakxA)|s}A*0 zqT58q&j@gOMgfkv_4U#KGJ%CS{uUT?#a}-S3cskS#YcbI^kZeqz)Jb_(h=$vSji}E z9T~k#dpG_BFaEJvSePkvq%`3ER^!b-?vfX5Z?u}BhF?=@mjCz%j1EhgJSOM#tCTX1 z%1A{y8L>Yu?WHIy8q-Pxa~J!4bsz2uK~lQhdOmFzC#z=yVMV?G~k5Zyrlr4JHh0 zQ2qnrkF#hvGNIPdqF=eA%VY>VGYU?}wJsX?dnR&kvpF9NREQmG%>%ywOcZ~*E1MKA z8;P$6PFy?m`h&N&(bied9g%s~P7C83ByjeDeP zTm+_%<^LU~678V4I?sGDdqAbO;+ZM>Ig;S%gD1W%xgo(lJN=6++qaF#zRDFmTUw^7 zmiq7GdPyzLqE?!vYK}HwGZ3}7Cs`QSHv&a`dGZVjwaVqIJB#(d-Z*#O+F8F)JO<&K z(N9+TMfTSpPu`DkN5?`8x1CijW%!;}Wln9yxJ@&f`T3#J8+f?Dy1J z;}mMUc$4Xx{YC9g#x?TG>QxdSaK*u)5Q(!gyRja<(3L5L>PjAfJEdc#GcV_r7CLi> z(qe~U$=L>aToo)_v5&TFl^=g0+})@nwK=;DpJ}$X2{cR>DTRZVBwlE8L!n}ho(l?|ZrvExXy0V+7>Bf0yH-?#Bg1gPt_*H&l0JLXlq)dogV5+p{G1-&|@rO4-jOZWDgSe%V6Sf+mIx(I-oXHZrtv za*?-wSgK^>S3Sc{q-OMsJOTj5#IF{OC|sPw`yoxw<}AT|Mvj$$TF26ZtA!PFHN}%X-ij_S{os&aFE3 zS((m^@zif;)V2Q>SiYUzdH(AW{cy)n**{sEPn#SW&%-4G^9~cn_0>g_&c<$9S5C(H ztvBJIq|8Hnhma2zY@%UU2_pcD-Fq4R&bEg*4G*FEozH@Ge<`j8-R9)9Aun!Eu z!ATcSyFsNUEefgl_@a`8y?9ez2jlzIW4DvXT3aCR$uZ+l<7TA6-nw=VQ}I-& z;yUQ^ylt2F1$sDxrMQEUreWyms;@n+f}|{&G*YOc$;rvB9@e@jMssWMhA0#-KyBF$ zxObw1izQ)~@I3jIVBk|4sfw=blZYJdyEr%rf9Uk@dwcUoKGb*r?pYl&h~aOhg6_~c z(^lsUwk2~M65YngPWpE^>)r<*E!y}U6z_ob9%NQ8fTr;WPay?ohlu6Cea%X2)6ik4 z=ozrA%d4xyBaEgq)>BYGo3`QAXD5O{t$66$yl7ED!q+x(4PQ3=k`waezZUa+BriOP z3lmo{a4YCCfVRCq=aZ%C*;V z*}*mOOI_#Z?)C6|QES_goB-hP`hF*b$>k0IGL zfs&*l@5c{mZB|0m9Q^vdnem2fcLfEL_Sa{_2-Jo&sgTF-*qk9B_5wwb(6ey@vXO<~ z#7}=A+`x0Edml*KQRsiutj}Je!Q3@KEUm4Uw=CK>S5`Npk6rph2nv7sz} zNiO^=2|=+}%Ne`VZK(3NN(J2#*zHXC^w^vVxA*6cOBi}iXQwD5zLxapQ}3I5IFFO% zv}za2HqWv1n>tsj-+UHwqMg@F=>j}k=vhRSP5NG*kB=psgyBCrv40YSB5&p9Q{xZZ zBX3h;w(mH%Wu>KWjf`$k*rMEeW_&#S{`fGa;?vgfNV*ijiH#kKotjXavv(qWHcV;L zpbZhO?vCTKTN(b3^ol<4aHt)K>;VsllBS<_9(WXYSE^Z?`{Tmaz zE{9x~Ve=u^z_ZEO3wlEnZ8kJp@wIFJ4*A1^WBPO$)@EDjR`5}o12b_G%A&nvi2mRo z)2knOG3j~Q@uTWCsjwuC$z+A-@$Xe!1vn!ZsmfNH^DpJXa0QVmQqB93#!N zXnc!daPl!7iwd3bl6=mrX~~2;&77S8CUmDJ$Newjie`L9h@PhEF%MmgJ}kRG6)WfL zLm8*6tI*#Y)|Yj)B)K+hDlLR1`V7lj6$oF;gERG86o@o*L*sfkAj7rQ3wBN|`r}qS zm2yg=L(7%I7ZNQwpu(baGY z+KB1QoP|LJ{d3-=7Q)Ptc#{2XeZZ26zkV?E*Y=x|j%yvHiPGyyXMO&HM^Ee}{udP9 zw-D-w3%h6Bl9jHG?QrilxsVmR+_xIn*$RHhgy#69jm!6(pZ81z(IxIaoeUyo0 zU*&9)BcbSkyZ;7(?4Rd)$l65iXg_vHk!iml^n0@l9wkdY-AWO@`i=0#uUA&~_F-zJ zp@ZfclVHd4e87;7`gfR zuuDyj1K>R8S4)m&*C#8hdk!nD1UoxBDE@G?mq*^beicZ$*$&R6t}sXe$n7)IuY?C) zHeqY~FZs>;p=$?3v%;|q5}5SLd>{EbVmUoKE4{q%BZjo*!x#lboK3E3ZfhfgvY5Vw z)g_V?T658zs))VA7z7L$Px@W)k0Aa)M6!D(PC-Ub>&YP4<^yT53sq{n%1*eS94T!o zeKPZB-up$Vo2%z=Si(U_6kb#(YYhAQKNo%ywerpsF|y=G*Q%yvA!0tEdL26(Z|5%; z$r%XVLjPn0&osf6I}n^Gl<%+;ph(oNI!H)JO!rAU-S;^4?WqtQGI|L^RY}i=-oTtt zv?wI`pt6LX443bPSW1!4*iC*m-+0Bz8Pwr>y_pw5k!X`jiy>v>0{7cTS#mjGy}t{N zog%r7wp5};`YVDMdd9(FFq!TpUzQx9XhTDT$=n!qnxl^g=JL)&S&tDBK<{r0B0AN zL_T5%)?ld%bJ`z)ku|xEhgPa@W&?12vIdxmFtIZ4FY>Y-1O{QBEPmZv^6ikUk2=DT zc0#rZh73p&OK!5|KUt4EIVuINWM8_ntt|^8;{6*&vVm@gCM4lG%esvT8Or2JH(Gm1 z&klxYTM-HWpyP6A+M8j6#6BH&7N@PW!dTzn8KA%o}lnKMf z`mutH9ASREI*Qg=aLJRaIAP(5Ac<1wJB#A;GWBO4WKGz0WBEDK{V$z0^(Hvgx0S>)y`lpT0OIN?%-FaY>~OXh9I`AttcBqPaDPrzXEG?+VHk*SyEi^mRG}|P?JqvX$X`}Ik?E`Nxo$4h{)FeOb`>b<# zz#RB)v!hTO@^oP8e33Qt<96{I>wLl>drV#~iu|sP;&&8?;#$39K5>Lg%f@=ZvOhkK z-P+okt65%}s+S^!lb7NC!{F5073=I@*H42_XZS7R>wZVRJhER3zejegPz2sPf7ffm z%(>}fIEyyA@455R(<3Dg5AHX8>+1Rn7!a;74LOAc@va14%uF4xV6KPZJL(!< z;H+=V1$X&<{1{PKUs8F?i5_CV)aXs=dw1M)e%6cCyYH6Ys7V|@3+#mawTFsh6DzK@ zleLZs#Vi|}qM9}C5k8vmH7^9G^=dFo2V?mTZasW#$N*P7AD7E-ad8n=lfmB6aR5B? z_HH<2k#!B`f5pj)u{l_43+ub@34RU^1`)80xx*YA!R{9Cu*>D%%_4Y0EwJ*%z3v&oMr@@{Z^1QUWTn z#N;Kr7^Qvjf{YCPrfF&)y|w)#!=#l&qH3(vZ_5tn_vuEbFOCob9$KdL(3M&cZ??Cf zyun5g6geUsTTxJOi-?G<20AV^V)%538mqRX8U?%nDLs)WzOWdel}rj^w7bmu6HAd@ z*nm2^hS}{wHuw{7bgyk}VCBBx5YL&q7wZjr>=x9S47jk6P$haL_>dA{4zMQo6bxy{ zz0tj2v(OBt42qbgEv!v&6=?HHxz_h9pgUVGsoS3#VxZr2L!-n{1|h037YlVC;+k&l zH+ZRHq~X9<(jcw>zUibc)%X?OLM=Q4tdu*uKhn9R zhGP%*3-<=g7xm0m=&7oz1~)eHfql37iHzVO!goh?JX3E|B84lINM`*E?hRJrR_20;j@Gg8P3jUTbRC_jcjOi|>Xii*zb{YJ%s#Lvos)gnSievfjt zJ7&Qi3fp}68anV4yK74Gf@Alr^!xX}k9JK<4?-`-1#*r+!ho%uD2-p5oBL8g#bGKl zH0?Z%IR9k0%8maXa(xO@dH1D#hl*Ct6f^W?8x*$&`YXJuspv_0SE$$^N3bbhlHw^h-! z3OnE&#YiK^h4X<=Hgyy+H5RVxfyLNEt&`jC2KFpI>tlp@R&$ERS(bVIe-T>hcQM*a zsq`s(Gog3oul!)Vo0^^#tvVV2Um5SO8HVWhq6hw0Yi*_$f}Vfwn?ss*JcpmBJ?YMD zn#_*(128c{mWl?6TEjlerjZPOvy0skVluMO85=2oe-RQA5@WuM4l|!;zpHO-6ff22 zvErF@S$uIcU4-*)EWA@7@R}E}CW>SMPm0i6mp?{Y2{gZkJ(ORGb%>)x*@uT5FEyFn zT^)OYP*U@3dhYIdwY9kaMYTMm3}|F=oORtRa~gD|w9Z~TIl(l_bWiRIWkGBe&_!t4 zI?AA?v%A1N$QV~g3pDOJvLwj-b-~3xD4NXbe|_S%4JbpWwb` z$gSE|-Xc3ajfEN7hsNgu`fD2WN}2`m{lqLy3^5(Fv=CS5Nw2L<46;kdJ1%IGwcza@ zeb*ahO4MPfaa3;NOTzS|P1Gw;rEZycURXXcty{x!x^M|rHTi{%hoxKQ)6+78v)XJ3 zkuE36D>^Kf_h@IMh@Mv^Cn5Q5w?S7%#Mu2ak)sIxb!Dd~H+_`n0wIBZJc4sx%yTd~ zwg{TDbdL?lgPhhprybP+@e6jbzOQvMRYX=&wD335%ZC#6(-C%egNQsJV}Y;FyOWZz zmap7FSZDG=j#Ag1_R~%Y>)i-iafr|*7r1jQJT^by-4(v!?1OdjtV(K`eIDUoWJ)^4@-}il8-){0JZ178!<64WDKcsOJ-4tu*%mMA8*R_ZPSWt#pjLQ z9GL6??B>4#rbgecD00s!0bY*sCl{}k)D6W-Fe({EY!O6VZqY1eav$xa`#$i>N6@*d z=Ezd42~WEXynQQ6n14pCiZZjXo{~&Z!Z@nzVhw3ug1J9AjM77X4$U{$!K9SAT(3iE z#dO^T;?<0dDDhFlt{YwVlv<*{OvZ+l+%CJ0Ol}kQZNV5m!kisRM4@CSGOs7GLDxKB z`1ipY$YC$oh$d8yaK1Cu>izy77Q#r}h$bnt?3UJ@@NnOGy`>7~^eOf49l+8KPeM)u z{6XSrz9%?|2<9l186${oD->6P=)F+W1QSA2Tw^a&l8$>X=+;o8-*PRt#k?Xt1VaA# zSqmViVCmU~dH<*vG|Y4y5X~H+D*_>ou zMQ45N4Z_taq9v^FbpQ7&2WmGfX;^VJk}xb3YbQGr&=7^C;9&YG)k}iXN_R~T17gnl z{p;wZxR*rD0E=a&hcgq^{^Ll^vF#r6$CrrlX7W6;+()-u3_A@VxwFvz}fGPDzuOml@fQIh}uR(?27{9J7q{!0|Y4$ z_Lt&-VUQ_-(;k1YqI51*xGo_zt-Z<(_8TBWnD;y~9k;)2L0zsaMehp13xqYi&*rq^ z(Uc zV$O*vR#c%sKlMa-g^_45x9%+>bSzsI2YEhHI-L!iSJbJiAlmsLH*aOsr(ja+g#YjD zd{_K#-o?N~1R}JbJ3>_)k2oaJnsE{@t$s+sQr+jOK}wj}`h1d+06DNO!w88P}TR<~ z8D9pdOeQ8L6RTa|eMhXG2+0QWii!|4%Lz2gdFANdk(J$IL!gv#;S?IWQE#=GF~!ac zsvt?-b5|(kZfVs)dJk*kunyd|c6OotrstLJ@3piddLyu8?nUYrGOL|HOytZMQB9^X z+#DK2459w*5zz}1yH#yAfXTzp&+whB)AUvk&~$YRpJQV&PhAfW52t)axc_rf4#VqH z!>kZNKb}40fXpXH0hJpzI|?u6tMyWC1iu56^`%qPiz?N#pP#k_K*qeys@7m8&`!a) z^JN_tuP|I*_U6lA+m3pZHf`fRP7*%610YIb_OITKJ?$q#`lLaQ`|IPS)Y)!0m5~T& zpvu0F6qab}osek89byVI?A&?OCwYwJOY-%UF}Rjn+!P+(3hyJV+IEMrldjMEw}8W7 zP7YN6rdtsXQijquv|C?5WZxUs+wqYuE-g(=Pj5fWvLnTAf!d^FV~eyHP#=IK-gJ)C z4WN zD=e-Y5Ei8l6Pz_qy=eWGKVlgIc=y-mQf4)Iy%)mYHCg^k=ug`;To)cW35>6b(IYgCv8b&PkFQN($4iCF89vgJd`v}p;F1P}F zQ_70#ye}-f4k-a}VNOQXtsq1U>Gn+|%*gLSh~wa@mNYOhShZC;&;GULJyvti)>o}j zsoHIg`Qf&2Ns4@Y>IACmLHbhfeu zWnjY>whl5O1{UO7#S+QTrovDSSgjv!;(VYX;piwMBNK5A8z^kYrze&MuwpeRY8_jo zNMaBHa#cNDYTDgey59v$1d2yjoh|?8YatGY8M_GJebt~#?!I2kjGFU~PmuMU=f%J= zRQWJC&;aZHdJXpUiHK8#@b2-?r(g^t_{I+OuZ9FEe7Q>S*8Qe6L3`eSaz5pEjFHAW z6OP?d0SWk`Gb26Sn3MRWhbi*6y1G~EKFGbCP6A|*&{b#CAU6+>{L)gUqenkmr@WpB zi-l$z(P;0niAOeIv0^cd8Us;o;v)`e<_?6Q)wawMbYZcsujPm5%dXL)WTm-u=AA3> zR{$lE!mlgOl`Nt{uL)6D=E#RNTcxeFwSA89v$WZ~|KX*NqcTU*ApV7%LBRyPzS*CL zU5@PE@0khS(X02_@;*NkDgx!jPLnMsfsfhi1r1-X$wt=1L4Sv5;#@dqR?z_xrtFiE z1;gGBm+6lt;E(+~0OM<<>qsVol802h1o5G|C7!&))xX4eQT4G;InRL zG7aYU@8Hmcy674ct1dpDzaCK>z~HI4d3>TP_e<8X9PfqRgz(5#ii~43@KSWW6*lilmvu1_e&%USbtn%l6vq#Y(9@0(z~!yrLK zK#Ax0(1@4xsypPwH>W)oP%!Q0iuZEeN!s|L=OmpF^i15Iehzb=AUSKFOev`ZI93BJ zeg8IWKHC%A*@`S7Ww@Se*G|xH60*G!xfJt3R4zPHxq}VXTl?9iaiao zykXe%dB^jKIVc$eI;03KQ-lurKNcou;sXTO(zVDvN{U1)qfqo1E* z4D3}PHf-_J^u4Jyw-Ii+K11lQGDUGb-Anf%ah#c|(O;t*H6P-_4M<*d$OPy31;8=O za|d2oi_0G5o}KgZqPjH>UMvFoc&MSL!iHSM9iuv*{bWXTpxE;4r#j}*8)w0zi_R%8 zjDyNz0x1;gL8*BBkctnqbtGYN)A%irI$h-CA@_@hueUkFmGU>kITXbGsR6fyN?4&e0`+C}*l(6w zVLmZtoFP+7%7eva)WdFEvrnc>M!Xq$VfW=-SXCNN6RrU(46_`0&sqD>3q4Z z@3LKRz0*c4dHDDc-RK;wtTkwl#R4C@-ysFkB15E9eMX+1dSFvg9OwMjEGb-)m~P z%Jyx3S=M>JZIZ+$^0_e3R+2ZhB0&$&UFu)xNFoWk9`4bFY(P@OL|9lR4LAY@BuWC1EFJ>hMn1JV0LUA4(rJ>61__ z>lW@#0Z$<(iYM6)jB1AnX3k$8@S-~*oz(YsPfYjwK>YzHBZbf+c;m4p^z~$xia(+yN30wNpwq0!k3`(a%hUcpv>NbBlM3>(HOSpNFf&J2B}2{1g|@`!D?c)_C4)K z&CSB{PM8#C0DW!>vnnZw0PPtNpQ+Cy?{Yq^j9 zOm9vi)r``z^`m^<^VY7=ESa%+?wrrqbcJmIz>_y&1(dcEytjhXX=-Tt0ROF;dUbJKaBy(^*jk&X<2#Y!KB@6S0hlGb#vr^8>gsqN$BR1# z@k)ggR^tl`FWxH)H|QQ$h}`gX36jM&S6<8BX8ZU5X~La*{imX(bjAk2t{?TdcKPF4 zoA_?tUbvuN9P%U3pCC?nCsN4E1wUw?f;anV*8pFb)}}ip1rhu264!upNmW&x^%G$< z0?>nV5~CaI@SV88_PDH~DMQhFy;8Gg_uW<@2S-Pe>gwu0{cmU2{W4bkdKp$N2nqpT z4j^^u%s~*zLC>y~py~(%f|oEwyRQQ_2W3~NmXiLnFt@LDNDV}MVUy;x;vs4-&;o-Q z5zO=PiZY1n$FtGWO1<{%oe3voXJd<9iAt6(KxrP`zB@J)UMSZn3o2?N0!#V_vD8cL z3WGGF7w0cL1zYKAT@|n_n+74O60dCKD0=>&9vMC$oDp!UrM!zZ>UqRVop{6jVlKcz zUo{qqRC5dFSRS8fTV7v2kq!6^8D_+_IUA-S{CdTEtA@oO`tPHoN+6av7Vv|tgwaOFVL({&9RZr$zYoR&u4z~PH)`PepQz#VGfYjW zZ2hT@-Lt-BV> zxp6PE-@jdwvYo<29?HEWCNfMU?UYOQQnpxEYkT{c76q^7IZ%oLYhrq(s8~C}y~tz= z_?dxGEHvsV2KbQ(q6i80_v9?aw3K>$|5f0nm-ldx$jI`U=@9Bx%<2uB5F)|6n;Wp!_UZ77ZZV^V;YH@9Ivt{{GqG^2CfR6nH~)Y5Qe- zuA5-2tI{jD;>0P_RnyQ(WJAWbar6m>$BW}w4L4I7I7 zqtYcErr5yD^IdggH<^Hk^Gmz{t=XS3J4^~D)teijR+L&gTtNxU0!mH0viGJ_Bm^Wj z@0$a31YN8~dBtT3^Uma|!QZUPQpM`(@t+0w#kvGMJmoypw8N@5vQ$6>_I}6|$m44~ zteJp9bDWi}fAK&~1V7=LXyZmnFhr8ev>c%e_j$xh`br}sWXJDD=RU@T#6UL zNDFRTy_Lsv58J^3v$Z0Zt0lkf|WY7u(}?L5)v@t+*wW}oe$ z2KR%;oW^UN{y1|?9PI2rF!-0(AFeOP-o)+Y1d3M9(L(?1467O$ad>iZBa$ockJo(B zbat+xy-T3;1La#E@63yEplK2!16)Sk0$IIT4;Z5{<2ug37LRlToMVR}R)-ks&vNhMw=?y0Ua{&|q zNelq?-)=(x8B2)+8Z=1H4MoB*=B8*$HtW>H#72!aE5)~|ebA6z&GFY^B~%6l?6-!7nW@D^W_3ArBUjhD zZv_Q3+g);AV0?!H0{(}az{Zl3ul-EV$Pke(;97SMH@SbbZ(0%oUU_i5z&KrFXK#Pm zymAtBQOLMzmWDIuXS(6i0vZ({QLE_bp@Fn@tvF@J2ZVeQRdc;oJU#nvJ(hKY+MCmK z0qR7&AoB8de$wCYXxnfT0^i{}nI_L;fa2=^6WcA6G$x6m_rjBR)_yfhBb@Uq+y$Gu zw0xv^S3Ywvx4SSuj~v~fm^^dt^c#*9Bi!U;taZT_=^H?|Cd=$AI{m<6QgGfZ(!+P~#&=78=aS z*Ueww(Szj&k41`n_&Yk~KPU%6=7d$}J3l+FkR6Ueb! z_e!mslf)!3^hc-ZBbJQ#s9#5RL$m9F69Zf!?tpbg0qN7A2U5G(*w~=k#y+zer`gaj z0MEhFNf!h!*kRr+TS@wiP1ra%NP`A`rqW*iwbUdW5Xq5l=JF>1CK7Y#vqpwW`3$JM z-=klTdEe39zeJ$7TQR$ozf1Ae*w+uWf9 zd+m=}RuEH?G*i5kb`y#tKBY8snwFe_K)ou68WLvQZgs$f3#PmhuRu@n>STq*_5~o} z0NrHzj~vYNaS{`*{6#>fE}yZ<(=49=Sh3n5Q@X6KPQ7+Nf75#`H1b(I4q`4SR#v^g ziDwn|JKm$EzTWc|%^R`nJo4}zXZqUH5dny;XZm4FpuWC7x2TBj1V~QlG)4wCoW#m! zfWyjZ;_2UH>*?y|YA~y6Y3*r+g9ru!8X$Zldchot5J1V`J)na_x~b&6WqsuCqNL<3 zaXE7DvX=pvCHT{4&7C&BxOi}V51JXj-C9Gh(YL%setWj{=LNB~6*SJ{0_q3pbm%av<&(sv{|;y}Ao%}T1}ng&?VO8G-4flZ+a;8tII zhk^WQntszC>m1vbF9@5pQ_-_?>0|O#YVSs5YYMT0kxincfPxsAO!Pyce$21sSQxl5u`8haxS24tm3My)_Qvm>D7L%LpeWtzA(JFM*a0tlHWHy1YxJ#7 zD;YC3yu{%sK8YRzWF~EFG8Ytp5PX)`NsuYvB?ArUa~*eu1id2(!X5>+FHrEo0rGXg zX2ZP(hPZ+{2w<`oBJkTuQkT!pd%b{riXuH4+*yq=El>KIe5$&Rjj zL9V4Jm>`}yjsGjfOPSaJ{1)WKEe8@@B#&YikR0dgEo-ztVt%9ngC)nX5E-n-Q*^G7_0^iv}l){~d*M(pM>hjss9UZ;NSd zxzM-x>w+}PFEb|Bo-fnDS@X9Wg9oejJXepWG^}djwH+t%x=TyRtVBK-o{`Pm{yMS% zg!Vu`*eV>VSsv56&$aAp_{%FBD4PLq{BK55&Qje=7;X3Id8!%r^Yh&1|2x3^Uvxg`*)w~bP1(vf4I*TR%1Sml*+ecm330e|<%~F7 zG7e|_U)T5h`~Ci_haT$gbD#UX$Lslej`zc>x|jCsI=%}*kUg50FX|%*qYr{GoY=tv zuds3Pd%!PdpYxj6cfgO>c+kX9uB^@Y`pCe zw2iN)n}@HP^GzOqdv70S4|g6(Q3+8=VIC)6Ur$9bvHx5k>f!AuCbN6>1A_1%nitPq zzx`}>L?@c$5IQ+;r4XeqiLx`EwnnJV+ATr9y3R~VA3~wDY838=Pq$}Az-Uj&MD`==JoF2z?{JNOd%R+?^&anYc2)}CWuTo zTwz;U-axjQv1tXj@oz1KS*|EwYW?9U2xFPasg|{X-^f?a4h{rq-g`1t>8cItBX#33 zhtdv4g_vyJ{K_~I)1XS5a5P~|rW}2FV)57Vqz4P?Iqo1pPt=Q0@i6}O9JBIYiT$KFt=VdRB?yq937dW}O3(ZA4y`I(3j+|JPOUon7 zeq2xv$i#A^+_W?CKRY@e#>B+*raWDL+BuFZe_-O_W`>KOusYOPuUbNweGNbTE(QHA z_KhU|(ue2;B0PMNXwAHyp57_V%*hTfjw-@zP1CKAkYf25dz8k3_qbQr&)nlI9C0Uq zYY6h@xMYL+q>Q1^9+8Z@vsA_21e5Oad|q9x$w{|XfiJicFWh;B>Re^&ZBhO9{OI#y z((z(Ed3q`EOyZR(j5w=QmLBfoa8k-Ckw*R_4XQfTghVtU6)zwlaGaH83Zr9r$jtsh zhJQ$i%CqHeCsg}aB6%s$^>i$`wl%P!(X*+=y6j76z|>OUg+6J=P>N-#+{{v-LAQNo zl8P#<$ZbDAnWF1w4iWOvQaV246%5shc$q-BTYYV9+TdKR``Q-ykx7Twk&)!KqH$a; ze^Q95S4c?4Dpd~^DKSH(!95!AZ6z<#{jm3JOQSImfk^|_L(Z)0(w z^LL0<;uZBg=jqYWo1+`ztbJ4%;T*PmVZF^uxr)6-oV7j1TIlQeaT!+Im-ZbC$||>i zS&P^|#VEPY8sKURb7XGSlLw`{a?O_a?i?u!`Y0ZKk0hYMG$Fw&&&un zOvSr@tDE+(b3UqticI~@hlRy+=1Is2ZE7kDl3=~iPhLtr$FqacBq`USvcRygs`nNu zk-8c;AwW5|zvxsMQ*|6(=D2!K+ho!4%A8<|*3E&n>t_yISXk(+(_%8UPG`B^xu;*Ew0Ry%-dbwn2IRGBqRyMkhBB1 zR04Ah3lld_2qt&ZS1#q5{nJi+3rkA}%xYt4U_*&~OdnGF+V5G`MK<+R3H6<=;)`ob z1E#KN4r>`k4gAaW`S?Cm>8Plrq-8~GEOn_; zq~6l*!+mj&KmNU5;Lbbo>y<;->qe4D@-XQ&Ul-5q|M~m3qybl}xLulq;*-GPTc#5Q zx$ym}!lbINIpf}MXpV`L%8~^pjx=Wx$y*OidIRUQZW_+V8@5h%XXO*-0(E9i#@DD= znmN8Ee~g*ZKZrB~;u4x9Q^P+zTeo~BEA zug+)o*+8z4QX*w?Ks}YZdQ2{m>maNjREw(;zbAX4d3NTc$cPTc4R>X^i`>ZPa9B)J z_Gm^c!Tb>IFZTfziOjd^*Q8|zFUwVxd)E~G7_6wz)pwrTgHFo}Y{+~k{*)vi|8&a1 z_&y&BV=A)RC`P~>j(qXDe_9{Qx#xMursmN_C!WQSu<>#M^=DZyU4FuLZ0cra+3hwa zOUG4IRgH=bJKgGBaq*90&Dx6A{S-4EdKY`cm`x{^!zTj_Ts$EH2W=rzEs;&lkAx zZD&uU5||tv9aAUP$MUrKNW2x0?vi znYp*8+vh&VB5mUZz>j~Oin59d=U$eGhtxf-DTCkKtNVzq)r6X-_Cpu#jj-$7UvU`pKKNLQug~H-D@%m0s^$d(MhXkKbUZUN zbFWC}6@dct{))-T#YJD^g6qBBkM%OAYUj#`l>EtEg1xWtKZ{bNIlgI&Dx+zhZ!=R8 zpZa|Z(Zrd$^R5Ko+g?(!r4L)I#h*$qIW8)dlG6rdpg|emmnyT5caN~!d@dhzSUqld z*55S2d^^_5m-4N_SMeS;mYaKQT^(`1pK|YI9L|Oe-O8{~rfRCb$EgUd`ywam9d*O^ zlMv&{aPD zrll!m#Ax?vZ`To3dcsC~Pu;*tifUGG!!ya=K}FJUrvG9A2S@>HOQ9wQ4!3!6^t)8~ zt{%dAjRkxwPP)(%`rTQ_jp-&TNga1SPPSKaQa)x>-tz6>FX)sdrKNrNMhY|~?y*`| z1hZX&wOMv(Si+}of^a(VikVd02?Te6mFeo!Vx5VXf@#L_w!X`*$e=@;IHEo@Y$tk5 zSnjXsJMlUzs3$UVPsQ<60aB>bxE4gySwr&h%)v2w|B8UQ<()6 zXW%&sse7WzB;>)B3fNR_Dw zbRGY?48mlIf&N5bgDBpwdf6wvD-YVF8{yLnU$w>uR_W)~O;t@NJUyqE);<--nv~+l zn8j@AlIUkjzfYc22tL;GQIGUVNnVv+!W@QGP|LS`U`EKa1651nq zzu_>pymq8SyYD)ix}=Vj!2<9Ppey36+s^THR@kNG@fGw&uLPp$VZ=6ZI zUT~dH^BP-6g|Mbv7kS*<#K_2KZ8S;@Uhi#IFg0+MK3-c}8*hV>)G>(VQqeEPYhlZu zWo6O3P_8~;lu6P{LH#D8UW8g8f&ihl<`Z3rVmq{71Gq_Y#nqieE~*KfU9HW5vp44v!=*B4EeYN-Tx3uAuW|N1UZ z%BsS`vJC45*wkGP291>NkZIR%)O5dp=I>G`$8zuc_v)r~+I%AiDpT?bTC8~pn11)_ z@!As8;v_dh61mp7*h?BY3tVedDlg#I_dAU%qq~86@r*cro7{Fz{Mx4a^inN!ROdA3 z|~S$~Y+(h_p=Yy!#`Q_&XKZ*k|xH`;Uis_zGjCHH(#x zu}iL{W{2&Fm*ms)cx?K{g9BSzU$4z%Ryp*{Br|~MF`&Srw$2+m!ZlL#Hc{D8(yH3B zQ~>LBy|AAx^2bFqWuAont*qCpQ?1cNO!hSp;wpAd>>PjN`T|e3&-{D^1FURe!RNhK zk#~)Xd&SOcsFHuGXti#T7-Vv@5+sLneVU`UN?E+C3B$?D64{xG&!wLz)66QN~AMZd|QM z_#RJr4)-_A3WbJ+FsiS<65;2!oqMwPrwr@KJ(o+!dFMj2W;;Wg#E499_l)AFsVXBy z&0N3eg#A>=Zy?&f!;JJsP}<+qwQJ^1U!Q@WA}4@IpLE~Emfa@-WY7@a12ha8g-+b3 zj)W_n2rZ6uH~c@ii29e_`v**$IaeA>fxrPRCO4ALv`VIRIVc(^hHt2^536eH+$DIa z4&uDb!kSV^pRWD^!got79(9!3k7dz7-RDm^hgb22YVwB8o&*$*moA^BRxF5Rf6=h4m_OuNLZ)1|*`h z=H*fO0hrmffQS|zADlQVR117WR8*8s>V1n_)c=-m&|6p{?!Y^+tIhFU%mtxMrNDR= zd>m)V0ht`wYi#O)tG{yrZ)(hw>`h#%3Gq)ecjNtzhGyyJXJ5&E3AN$P_+k5e)QX=O zyXJ&qhHhYsW;~j(o2s{YU{u~=XrC}(4fOc2TKB-pOV@H7DpQYT_W+4vUv1r|imS)} z3ln>(S*l@~mQjG*skXAm_kdafoKMXw0K67yz*tufX-Bw9^2nby0v%9_HvqNFrcPH2 zO?_{=Q!%CXCa|{@6%;h7eim{*l*d`=Cd7Quy|A^y1oxK9YO}T~9-u?=joWgr@hWCX zrwAAs6f4jU+y<}~T|GT}0>+JC*kEat-*>vKZO}fO=_Zg~*pF+&;?aw-<&y!2A{@E@ zprMqeoXq&xt%Kv<3L9HF8qeyj0KADN?M`Z^lRHz@2o!2xxOuT*U)rkl|3o((wTTdJ z5Lzx(UuOLeW9Q#pbRh0;y~;-4uz({2lf0IVI2_v( z8qc3bCCMlG8fax;JPv9DcHR!Vt}nC{qPhX7*U~#)geN5Ps!`ljKqL9$Z>`k+#w? zwbf5blvmK%ub;7Zd9TJrhs77&-Q7zo^R}8ETtVCox|gtye*+?gn3sN29Q6S+>+AO6 zr(-nBcusUjpIP>iS$1r>sF+w^^MISIW0K=H>J*hWRlcY71M^(SCDvA1-cWnao9i&* zDAMhh{!f_OO{yYu!s@P{0XGE5L=E=#(sUx-Rml?2%N*19pGo4`0lV1Lj3bOQ@&y1J z{pKdEK@CtRaWK=qiic!ymZkahUMa;l1<`sUD3DyXK(fT=1@<~xB6mJ`%Gg|lZ3 zfmFaeLH`<+r*AlD>vf#4Ue0N$w)|O29#%x^63jr($Ub=I-2PWRP9K0uuWH`?Rzxun zO+FlHQ`=xJ%8j$jAjbe5y-aUdoUFTnQ{RuYvBT_PiFlDi*CJnt<>;V6coqDOpS5$J z#iL)?n@GiaLF>1=bBEE~!eS2r=*&KIE317$4d2QvOFiD&)n&h;g^mDsy=QS@-PD4M z8uCKK2c+psA>IHL^taTU)6&kEDzq8U79ZE`%_Z}HK(=8!YwL4%cdY}h;liVMe?i~O znx3#<-~vc(SLZ*(6muny8CiZmHB|tP%3U6y5L3o+_LH~Idp)pGJ2LO8#CF!X3{3#W zvBzN}R5ioyYVEN_6!SU7C*JE1)8u2Q*rg2@sPWJGA`qO(~`pv?2Ki>p~Psm5K0>ot;h z60WlQ%J2MR2?AIgioxrN;l;Ya^ZQ-o+>kV#8;%EK)!ud5HYg_;*`bq?^)3kW8 z3QY)Ho*6Rj+<{c?Z$;eF&)1nm@T3faP8*|b;)uXiKn0|tBW=bkWf9}QP%PH$htqKC z*K+g=o(?53SINf+Fq)$xr@|t5uKuBu`T6EE@uIv$L}oCp!`Z6rOS*1Kh0-tdO~vpOb2wEp}@n9Hp}{?!a=UFkq)8 zBpd=`wgCG!Y^xDIn0U#Hfyb6!o7=vN?n2Y`CvfmekpLBu%lrflBKvGF63%dPsV1&KUciV=PySqNeO`C+^vk^j$Pgi@0w?uTS}DrZ21@f6zG$e$HHX-j)w&=sLoZ7 zltt$=AvjO6W&|;8D?}x5(aD9E+HYhvwOw`P9M?GGa(DGUFH_1sIEk)rqq^_YYUTiAmN8q9;-GMs6AGd**o*%o`_EgP-8Ma72QbDb4zgN z>QGfu>MZqbF0rXSfT*<`;rA)EY630#!7XSq;}$;#%KQ24-3bZ=%#u;ql95)(pkCNo zU*hh+6r7fZG(9E~ZI2&(ep!1X;zGH2s+nwD-E5<=)WN1%aoJOhWsiIF^Yf{Ga0yWIOuD7ryvK0&&wYZ1GYUUuT6higj_5Mj->c`{F781rQ(T<>qSER=|k; zd)^M)73D|!kmeTS*Y`5vbFph1~#6w z_N~}9tVq|*j!?pGiC`I#nWTFiUrE^5rI{hAhOJGS6tw_P28jWJUt$4w_~-NOA|8V) z@5T_s?c+N;cX^IaI%5aHxQF1PJNN2sTWRO-1(y*-Toj*gk}tAx@055-NPf@xT{C9~ z&b5o0rQk;DZh1`YW8ZsU=rBvdWi|{p-OG!cJQdl=YrnN}S-Fc9x$p9$uc+D?oozqo zZW+9xv({=Xj3A!uyUixdVawo4lh?$%7ZxYOHYbIS?pkFJIsW?)Ny^j<;V6Rb9N*dZ zeUKZr8ot!ZRU9R}WOI$J*qjGxUbE*a8ManHirj4^9SO_5t|gm``KlH*YJwWer-x?V z*5BG1ef@J309MH~1t*gJVZ`6cZSTGt;M%ArWu}dBZ_RUqG6gjr`YVFFsQhws(yIS7 zYGRdX8C!qYC)}d)x+L{`3t*~pd^irqQroJj|s5>QcEXFc8 zuwshWI;;HU4(Xau@xB8LG*=i4s{P9>k@k)1ErvT=I~2!Y*uD%xGf!IK*GDcRX=Q67mfrdfMdvS7-_9gx*@KZ7n|FJu^P6} zAd4X!IZ9%aOAV9|kDE%J0bT9SG!R+mU=Fc9E(V(I$<6>!d8v7+{9zch*iukDi1D%7 zxn7#B%puaoZAEX_Yc_&==_aCyyn`rZydH?0b6SP476uK=l5^;w*g_q^nO3r6LS{R6 zzACbeeF08eovn>zay6)}&Wc|AV-4E^Qx{YuS@_7r?fE6SsqWiGdpw;U*dUm$Uf0Tr z6FcrcL<5U!q=>Cs5UEf}NyA5x0h?r7R0DJKCY^r1yqv3lx>6+dseBA6^$;7#?Ctxw zKC3WTXZwpp#x*nCfAQIU0NPFFZ5>8Zy@1W+Hcdou7yYs6bo~e}9+9^s=-kdsK9*JS zq0p$**Vp&yrgFgVFOA2uIRj|2Da%LcSJ=yK0VQBk`=`s?POr2B3Y!=0?iPI`a6 z?j$KNqFtgeJo}kK1zFQeO=xyB8Muh2hn=WMMHLm1H#jYu+1icK&}P2+6c-O$l&4X4 z*j9>h=*eAKjwK`RLaZ0SKL$Ms$h-44pkGF10SGrd84ON(YEiP6jpU%d<25 z_0ER6n5bV5=Tk$0zM*ST>2j7PSx1$eQq44h;r;-(z|dRubKlCZcrT=RPN<&F<&f1E z_H#$t@uzZ6b0N$7%DL(y?dFbXV6%d624h+P{RZyHhbhU~N)cJF1>B0TSFi)kPPbF( zM6UU_MyBO>OZlvt{@hc>yUero5kTq4s?!b*P2u>)p^Le1yu_b zo<)#lq=Um#&0q8fOWA?!;`*uKPlKj>KFxQI?di=5-aIv;(HsvO*s<39l;QmkT|ZlW z4<7C^qcgtmSHIe@+&yX)maV6OeK2XkU-ZWp9-DJ0D3v313e>tfhji}g;P6&I%7x5i z&O1jrg%CveeB=eS1(=XQIrtKWPUVp+$=i_`zWADLXK#L<%!m_2n(x?ze1FW7+WNyf z-WnlSM3Jw@p0e!l6;eNj7G8ZHCbsxF=sw+n9;>M6u}FT(zSVHcNVLP|L~5()DLFws z3?rgZF6igYnDh0(K`0ZGqW`?=anC;4n*n6 zS%9Oyybo@zt8b7yzOT7fJ6a-x%5~wAJK8tcLXmm96q+xU?5hYsdW}sp-i=`Swu#Te z-24>~J204L?Jv$7uemDyqLiT|SRV#Ntfk)CtA1KJ)raE}xYu+GK&4yDD%O>rsLO?f zczg3|GGEZf8Y>qagTluI6#iuLuN%?+3}$hvn%tQf+hdV&Ljw?{`1@l9BpI89+r-_r zQ6oHrO1H~1wN?{VHqMoInD1vkM1Uv_tlHWzHg!;}yIRF7Rboh0PxqNUN*!KLn)^3y z4esg`rFlOgsqd0rqcrywlVd^T*$EHjraX)|SsX?>w97UOXgdt$VL2}y_`D%k_2v*T z=-fsp%4LQDkGIp7eU@|cZ`ONv*ZU;w(wR7u`0-+M)JvxyD+j>8b&~GgxXbfFga>Mn zFS(ncYzxC1DS>b_r~Dz%W|@<@rL>Jk(_+IlMb=gvt&t-1i1$X}4FF4VR=?0tOQ%!@ zo^z}hdgzrtG5Og`l>cxh4p_bPAT%d1SaHvE8Y%fcvhV?o>=%ZGxg?V9-{(E_lqdEf z>L(&?(&K(|b=(7h}mHz9IZzGj+XfQJU=bfx&D!W z{|SCeM#K}@YcE1-)tW%Y=A73h zw1OC#BR|vQt+LiX8B&fCDZwLuQ{m}q1~T)iN5JA%{iQqqNYYM@aPw0OP7Q#iO9JhV z@lFLy;eHT`l^IU)6)5vCSVu*lovJ1Kd;*L3KRFnP*YNZ8D{Jf^*x5rCXZuQ!@L+L= zy=pH~tWi*j^HZ7=OLbvhRWIVd0#d@0Kj;~oJ4C814m+M@w^!{4T=r*KtZh>;%!na~ zb!(D_*|bwGQ<;t=DLA*d$TuFxFnC;eu)*E6_^#4ThN#X{S({^iUm<*qEev6Xp4~x4bd1a(OZDFc>NSOCTo`HlvhR z-y7Z9w5Xq7uxc3gJ+RC;=~dp-5$UKSzdn7#YtpMvBHGo#?^$cGCs)FY6WsNyH4zY8 z(p~qG_0!d!!NEmjGI_CzLIV#NOuU*It3U3F!omtv@>1+DiYs=nfU7F%5>1M#|Ko z%clrPVdnZ0w7tQ%;IE_PuX0uc2?8;4CQ*{WN^;<{;lK(}_95hptW2Kl9pZ7?@zpH% zFu3<#&-Nm^e0M}2y_Ehi;uog5*5wfjay$Fqk*=fVR`*tr^nB1;&QG>yqy|;cN-2`h0&_R`hqA80-6w6hOJ}6 zo;B`my$T{l&q{2V8+imHD?Pwn^0+a$xnQ&_JZf7woCVC}zQAFEFSv2$># z11o0MKi&E*J-~$UsN0}&2)xfA35~}njN;#SEWfgTCKx?{h5#HK!*}91w@HU~{xX;5 zkv4kLk-i!+|LaF<#LqJ)BW=v~H_bWpuc%83fQbOPfqhyOZDZ5&%S3sQvCMb(ykcjU zw260Bx>N=*obRsq$rZMjMoc+a+H-AWwTnla#U^`kT#6ytxzy0yEGFkaj|Fg<$ncP5!|crQH>wFb z+c!KG0!~I2$E+mo8^y1&9d7cMO|29bq6*kTVhPiPO=Uh;MrH8goSff3PXF-t$Arze za*$*|DSM*N-qgig#Xu3md16zbQt zHTTRni_H(wn?09J3-d#}N2#yKy<-B!9}d@2-eryzzH-_Hr+eZ&jS zol5BX3J-P)8B{536Z`uFLVHdNbcg@b{xXgQX%gj=YszbhGa zHXQr3pj=Z@=Q%<4`{}C~@9xkaLo&vo9g{A>ltZKf`bvr$Z!P|WLIW4f;#u&w#x92i z9!Kfz%bfGOm7I5sxrw7#;mF|+Y!y049>pq2FvoMI<)scem<;>^zBUbEL)DkYrN9ud z(I1^+DMLT78dL}VZux*@Y=hD*vIV(bJTMWw^6Q5_O(DkTNoODOfqQ;;A#liVJ6b)b zmmMl=7AgE>Q_%|CS)@#O^r&GA(|*B=lKvNJcPeAh`JGQ;V^>y6friqaeFM>+-$+5$ zjbg*Y>ep(Y-`02Z^Fg5AHC*#EXL8?FuJ0AO?{lon>tW zPao2zcy~rPgOah*U_y_vvl62Z!GXmFOFgg?0I48#hclm3UK;&mzl_XGFl@W`{1p%9 zIQ|9>D1^fPm+6f15W&yvidQJ=zs;i!Ct4Q|tU($j_^6i(r+S7gtO=NR;$M1t;^53l z?a184u6}V=6MBjddEOD0Qo_JhkBs0UwSuSHFX~ zO+E(lh}5&WeqLTDKt-Djyc4JftQ6^9;Ey>m!u)Z5*x{hy!rsX2b7xsE)Cz~IT!iht zk^%9P@{sR|GSxqa0@v!AXi6cIO2nRhVJjEln1dRP5Y+MZHuiGQ&T5)c>$+<;`q(YzV;Wbvo*`;FJd(x*2izsC|HlAD-V$xNEnU zJyBRls+j#nkCU5SxhrDKLVsDpXYiG2vFuX8Yn~l>x=A|`!0mhJbDbQrpXi6mND vBF(a}xa|MrHMM`o9setw|36?-wzY2or!f6NYKH{8hiIznUMx9pefPfrijC5x literal 0 HcmV?d00001 diff --git a/docs/function_usage/utility_functions.rst b/docs/function_usage/utility_functions.rst index a6c54025c..2aeece071 100644 --- a/docs/function_usage/utility_functions.rst +++ b/docs/function_usage/utility_functions.rst @@ -58,3 +58,10 @@ Probabilistic binning .. autosummary:: fuzzy_digitize + +Estimating two-dimensional PDFs +=============================================================== + +.. autosummary:: + + sliding_conditional_percentile diff --git a/docs/notebooks/cam_modeling/cam_complex_sfr_tutorial.ipynb b/docs/notebooks/cam_modeling/cam_complex_sfr_tutorial.ipynb new file mode 100644 index 000000000..caba366e3 --- /dev/null +++ b/docs/notebooks/cam_modeling/cam_complex_sfr_tutorial.ipynb @@ -0,0 +1,362 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np \n", + "from matplotlib import pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate powerlaw distributed stellar mass" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from scipy.stats import powerlaw\n", + "\n", + "ngals_tot = int(1e5)\n", + "slope = 2\n", + "log_mstar = 3*(1-powerlaw.rvs(slope, size=ngals_tot)) + 9\n", + "galaxy_mstar = 10**log_mstar\n", + "fig, ax = plt.subplots(1, 1)\n", + "\n", + "__=ax.hist(log_mstar, bins=100)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model the quenched fraction vs. $M_{\\ast}$ with an exponential" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "log_mstar_crit = 10.5\n", + "uran = np.random.rand(ngals_tot)\n", + "is_quenched = uran < 1-np.exp(-(log_mstar/log_mstar_crit)**5)\n", + "\n", + "num_quenched = np.count_nonzero(is_quenched)\n", + "num_star_forming = ngals_tot - num_quenched" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate quenched sequence SFR using exponential power " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from scipy.stats import exponpow\n", + "\n", + "b = 1.5\n", + "log_ssfr_quenched = exponpow.rvs(b, loc=-12, scale=1, size=num_quenched)\n", + "ssfr_quenched = 10**log_ssfr_quenched\n", + "\n", + "fig, ax = plt.subplots(1, 1)\n", + "\n", + "__=ax.hist(log_ssfr_quenched, bins=100)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate star-forming sequence SFR using log-normal" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "log_ssfr_main_sequence = np.random.normal(loc=-10, scale=0.35, size=num_star_forming)\n", + "ssfr_star_forming = 10**log_ssfr_main_sequence\n", + "\n", + "fig, ax = plt.subplots(1, 1)\n", + "\n", + "__=ax.hist(log_ssfr_main_sequence, bins=100)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Build composite galaxy population" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "galaxy_ssfr = np.zeros_like(galaxy_mstar)\n", + "galaxy_ssfr[is_quenched] = ssfr_quenched\n", + "galaxy_ssfr[~is_quenched] = ssfr_star_forming\n", + "\n", + "logsm_low, logsm_high = 9.5, 10\n", + "mask1 = (log_mstar >= logsm_low) & (log_mstar < logsm_high)\n", + "\n", + "logsm_low, logsm_high = 10.75, 11.25\n", + "mask2 = (log_mstar >= logsm_low) & (log_mstar < logsm_high)\n", + "\n", + "fig, ax = plt.subplots(1, 1)\n", + "\n", + "from scipy.stats import gaussian_kde\n", + "kde1 = gaussian_kde(np.log10(galaxy_ssfr[mask1]))\n", + "kde2 = gaussian_kde(np.log10(galaxy_ssfr[mask2]))\n", + "\n", + "x = np.linspace(-12.5, -8, 1000)\n", + "pdf1 = kde1.evaluate(x)\n", + "pdf2 = kde2.evaluate(x)\n", + "\n", + "__=ax.fill(x, pdf1, alpha=0.8, label=r'$9.5 < \\log M_{\\ast} < 10$')\n", + "__=ax.fill(x, pdf2, alpha=0.8, label=r'$10.75 < \\log M_{\\ast} < 11.25$')\n", + "\n", + "xlim = ax.set_xlim(-12.5, -8.5)\n", + "ylim = ax.set_ylim(ymin=0)\n", + "legend = ax.legend()\n", + "\n", + "xlabel = ax.set_xlabel(r'$\\log{\\rm sSFR}$')\n", + "ylabel = ax.set_ylabel(r'${\\rm PDF}$')\n", + "\n", + "figname = 'cam_example_complex_sfr.png'\n", + "fig.savefig(figname, bbox_extra_artists=[xlabel, ylabel], bbox_inches='tight')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Build a baseline model of stellar mass" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['halo_vmax_firstacc', 'halo_dmvir_dt_tdyn', 'halo_macc', 'halo_scale_factor', 'halo_vmax_mpeak', 'halo_m_pe_behroozi', 'halo_xoff', 'halo_spin', 'halo_scale_factor_firstacc', 'halo_c_to_a', 'halo_mvir_firstacc', 'halo_scale_factor_last_mm', 'halo_scale_factor_mpeak', 'halo_pid', 'halo_m500c', 'halo_id', 'halo_halfmass_scale_factor', 'halo_upid', 'halo_t_by_u', 'halo_rvir', 'halo_vpeak', 'halo_dmvir_dt_100myr', 'halo_mpeak', 'halo_m_pe_diemer', 'halo_jx', 'halo_jy', 'halo_jz', 'halo_m2500c', 'halo_mvir', 'halo_voff', 'halo_axisA_z', 'halo_axisA_x', 'halo_axisA_y', 'halo_y', 'halo_b_to_a', 'halo_x', 'halo_z', 'halo_m200b', 'halo_vacc', 'halo_scale_factor_lastacc', 'halo_vmax', 'halo_m200c', 'halo_vx', 'halo_vy', 'halo_vz', 'halo_dmvir_dt_inst', 'halo_rs', 'halo_nfw_conc', 'halo_hostid', 'halo_mvir_host_halo', 'stellar_mass']\n" + ] + } + ], + "source": [ + "from halotools.sim_manager import CachedHaloCatalog\n", + "halocat = CachedHaloCatalog()\n", + "\n", + "from halotools.empirical_models import Moster13SmHm\n", + "model = Moster13SmHm()\n", + "\n", + "halocat.halo_table['stellar_mass'] = model.mc_stellar_mass(\n", + " prim_haloprop=halocat.halo_table['halo_mpeak'], redshift=0)\n", + "\n", + "print(halocat.halo_table.keys())" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "from halotools.empirical_models import conditional_abunmatch\n", + "\n", + "x = halocat.halo_table['stellar_mass']\n", + "y = halocat.halo_table['halo_dmvir_dt_100myr']\n", + "\n", + "x2 = galaxy_mstar\n", + "y2 = np.log10(galaxy_ssfr)\n", + "\n", + "nwin = 201\n", + "\n", + "halocat.halo_table['log_ssfr'] = conditional_abunmatch(x, y, x2, y2, nwin)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "logsm_low, logsm_high = 9.5, 10\n", + "mask1_gals = (log_mstar >= logsm_low) & (log_mstar < logsm_high)\n", + "mask1_halos = (np.log10(halocat.halo_table['stellar_mass']) >= logsm_low)\n", + "mask1_halos *= (np.log10(halocat.halo_table['stellar_mass']) < logsm_high)\n", + "\n", + "logsm_low, logsm_high = 10.75, 11.25\n", + "mask2_gals = (log_mstar >= logsm_low) & (log_mstar < logsm_high)\n", + "mask2_halos = (np.log10(halocat.halo_table['stellar_mass']) >= logsm_low)\n", + "mask2_halos *= (np.log10(halocat.halo_table['stellar_mass']) < logsm_high)\n", + "\n", + "\n", + "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))\n", + "\n", + "__=ax1.hist(np.log10(galaxy_ssfr[mask1_gals]), bins=75, normed=True, alpha=0.8, \n", + " label=r'${\\rm observed\\ galaxies}$')\n", + "__=ax1.hist(halocat.halo_table['log_ssfr'][mask1_halos], bins=75, normed=True, alpha=0.8,\n", + " label=r'${\\rm model\\ galaxies}$')\n", + "__=ax2.hist(np.log10(galaxy_ssfr[mask2_gals]), bins=75, normed=True, alpha=0.8, \n", + " label=r'${\\rm observed\\ galaxies}$')\n", + "__=ax2.hist(halocat.halo_table['log_ssfr'][mask2_halos], bins=75, normed=True, alpha=0.8,\n", + " label=r'${\\rm model\\ galaxies}$')\n", + "\n", + "legend1 = ax1.legend()\n", + "legend2 = ax2.legend()\n", + "\n", + "ylim1 = ax1.set_ylim(0, 1)\n", + "xlim1 = ax1.set_xlim(-12.1, -9)\n", + "ylim2 = ax2.set_ylim(0, 1)\n", + "xlim2 = ax2.set_xlim(-12.1, -9)\n", + "\n", + "xlabel1 = ax1.set_xlabel(r'$\\log{\\rm sSFR}$')\n", + "xlabel2 = ax2.set_xlabel(r'$\\log{\\rm sSFR}$')\n", + "ylabel1 = ax1.set_ylabel(r'${\\rm PDF}$')\n", + "title1 = ax1.set_title(r'$9.5 < \\log M_{\\ast} < 10$')\n", + "title2 = ax2.set_title(r'$10.75 < \\log M_{\\ast} < 11.25$')\n", + "\n", + "figname = 'cam_example_complex_sfr_recovery.png'\n", + "fig.savefig(figname, bbox_extra_artists=[xlabel1, ylabel1], bbox_inches='tight')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda root]", + "language": "python", + "name": "conda-root-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/notebooks/cam_modeling/cam_decorated_clf.ipynb b/docs/notebooks/cam_modeling/cam_decorated_clf.ipynb new file mode 100644 index 000000000..4b698aea0 --- /dev/null +++ b/docs/notebooks/cam_modeling/cam_decorated_clf.ipynb @@ -0,0 +1,223 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np \n", + "from matplotlib import pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Calculate $V_{\\rm max}$ percentile for host halos" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "from halotools.sim_manager import CachedHaloCatalog\n", + "halocat = CachedHaloCatalog(simname='bolplanck')\n", + "host_halos = halocat.halo_table[halocat.halo_table['halo_upid']==-1]\n", + "\n", + "from halotools.utils import sliding_conditional_percentile\n", + "x = host_halos['halo_mvir']\n", + "y = host_halos['halo_vmax']\n", + "nwin = 301\n", + "host_halos['vmax_percentile'] = sliding_conditional_percentile(x, y, nwin)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Calculate median luminosity for every galaxy" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "from halotools.empirical_models import Cacciato09Cens\n", + "model = Cacciato09Cens()\n", + "host_halos['median_luminosity'] = model.median_prim_galprop(\n", + " prim_haloprop=host_halos['halo_mvir'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate Monte Carlo log-normal luminosity realization using CAM" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "from scipy.stats import norm\n", + "\n", + "host_halos['luminosity'] = 10**norm.isf(1-host_halos['vmax_percentile'], \n", + " loc=np.log10(host_halos['median_luminosity']),\n", + " scale=0.2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plot the results" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "xmin, xmax = 10**10.75, 10**13.5\n", + "\n", + "fig, ax = plt.subplots(1, 1)\n", + "__=ax.loglog()\n", + "\n", + "__=ax.scatter(host_halos['halo_mvir'][::100], \n", + " host_halos['luminosity'][::100], s=0.1, color='gray', label='')\n", + "\n", + "from scipy.stats import binned_statistic\n", + "log_mass_bins = np.linspace(np.log10(xmin), np.log10(xmax), 25)\n", + "mass_mids = 10**(0.5*(log_mass_bins[:-1] + log_mass_bins[1:]))\n", + "\n", + "median_lum, __, __ = binned_statistic(\n", + " host_halos['halo_mvir'], host_halos['luminosity'], bins=10**log_mass_bins, \n", + " statistic='median')\n", + "\n", + "high_vmax_mask = host_halos['vmax_percentile'] > 0.8\n", + "median_lum_high_vmax, __, __ = binned_statistic(\n", + " host_halos['halo_mvir'][high_vmax_mask], host_halos['luminosity'][high_vmax_mask], \n", + " bins=10**log_mass_bins, statistic='median')\n", + "\n", + "mid_high_vmax_mask = host_halos['vmax_percentile'] < 0.8\n", + "mid_high_vmax_mask *= host_halos['vmax_percentile'] > 0.6\n", + "median_lum_mid_high_vmax, __, __ = binned_statistic(\n", + " host_halos['halo_mvir'][mid_high_vmax_mask], host_halos['luminosity'][mid_high_vmax_mask], \n", + " bins=10**log_mass_bins, statistic='median')\n", + "\n", + "mid_low_vmax_mask = host_halos['vmax_percentile'] < 0.4\n", + "mid_low_vmax_mask *= host_halos['vmax_percentile'] > 0.2\n", + "median_lum_mid_low_vmax, __, __ = binned_statistic(\n", + " host_halos['halo_mvir'][mid_low_vmax_mask], host_halos['luminosity'][mid_low_vmax_mask], \n", + " bins=10**log_mass_bins, statistic='median')\n", + "\n", + "\n", + "low_vmax_mask = host_halos['vmax_percentile'] < 0.2\n", + "median_lum_low_vmax, __, __ = binned_statistic(\n", + " host_halos['halo_mvir'][low_vmax_mask], host_halos['luminosity'][low_vmax_mask], \n", + " bins=10**log_mass_bins, statistic='median')\n", + "\n", + "__=ax.plot(mass_mids, median_lum_high_vmax, color='red', \n", + " label=r'$V_{\\rm max}\\ {\\rm percentile} > 0.8$')\n", + "__=ax.plot(mass_mids, median_lum_mid_high_vmax, color='orange',\n", + " label=r'$V_{\\rm max}\\ {\\rm percentile} \\approx 0.7$')\n", + "__=ax.plot(mass_mids, median_lum, color='k', \n", + " label=r'$V_{\\rm max}\\ {\\rm percentile} \\approx 0.5$')\n", + "__=ax.plot(mass_mids, median_lum_mid_low_vmax, color='blue', \n", + " label=r'$V_{\\rm max}\\ {\\rm percentile} \\approx 0.3$')\n", + "__=ax.plot(mass_mids, median_lum_low_vmax, color='purple',\n", + " label=r'$V_{\\rm max}\\ {\\rm percentile} < 0.2$')\n", + "\n", + "xlim = ax.set_xlim(xmin, xmax/1.2)\n", + "ylim = ax.set_ylim(10**7.5, 10**11)\n", + "legend = ax.legend()\n", + "\n", + "xlabel = ax.set_xlabel(r'${\\rm M_{vir}/M_{\\odot}}$')\n", + "ylabel = ax.set_ylabel(r'${\\rm L/L_{\\odot}}$')\n", + "title = ax.set_title(r'${\\rm CLF\\ with\\ assembly\\ bias}$')\n", + "\n", + "figname = 'cam_example_assembias_clf.png'\n", + "fig.savefig(figname, bbox_extra_artists=[xlabel, ylabel], bbox_inches='tight')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda root]", + "language": "python", + "name": "conda-root-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/notebooks/cam_modeling/cam_disk_bulge_ratios_demo.ipynb b/docs/notebooks/cam_modeling/cam_disk_bulge_ratios_demo.ipynb new file mode 100644 index 000000000..818e598e9 --- /dev/null +++ b/docs/notebooks/cam_modeling/cam_disk_bulge_ratios_demo.ipynb @@ -0,0 +1,252 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np \n", + "from matplotlib import pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Build a baseline model of stellar mass" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [], + "source": [ + "from halotools.sim_manager import CachedHaloCatalog\n", + "halocat = CachedHaloCatalog()\n", + "\n", + "from halotools.empirical_models import Moster13SmHm\n", + "model = Moster13SmHm()\n", + "\n", + "halocat.halo_table['stellar_mass'] = model.mc_stellar_mass(\n", + " prim_haloprop=halocat.halo_table['halo_mpeak'], redshift=0)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Define a simple model for $M_{\\ast}-$dependence of ${\\rm B/T}$ power law index" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "def powerlaw_index(log_mstar):\n", + " abscissa = [9, 10, 11.5]\n", + " ordinates = [3, 2, 1]\n", + " return np.interp(log_mstar, abscissa, ordinates)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Calculate the spin-percentile" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [], + "source": [ + "from halotools.utils import sliding_conditional_percentile\n", + "\n", + "x = halocat.halo_table['stellar_mass']\n", + "y = halocat.halo_table['halo_spin']\n", + "nwin = 201\n", + "halocat.halo_table['spin_percentile'] = sliding_conditional_percentile(x, y, nwin)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use CAM to generate a Monte Carlo realization of ${\\rm B/T}$" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [], + "source": [ + "a = powerlaw_index(np.log10(halocat.halo_table['stellar_mass']))\n", + "u = halocat.halo_table['spin_percentile']\n", + "halocat.halo_table['bulge_to_total_ratio'] = 1 - powerlaw.isf(1 - u, a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plot the results" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots(1, 1)\n", + "\n", + "mask1 = halocat.halo_table['stellar_mass'] < 10**9.5\n", + "mask2 = halocat.halo_table['stellar_mass'] > 10**10.5\n", + "\n", + "__=ax.hist(halocat.halo_table['bulge_to_total_ratio'][mask1], \n", + " bins=100, alpha=0.8, normed=True, color='blue',\n", + " label=r'$\\log M_{\\ast} < 9.5$')\n", + "__=ax.hist(halocat.halo_table['bulge_to_total_ratio'][mask2], \n", + " bins=100, alpha=0.8, normed=True, color='red',\n", + " label=r'$\\log M_{\\ast} > 10.5$')\n", + "\n", + "legend = ax.legend()\n", + "\n", + "xlabel = ax.set_xlabel(r'${\\rm B/T}$')\n", + "ylabel = ax.set_ylabel(r'${\\rm PDF}$')\n", + "title = ax.set_title(r'${\\rm Bulge}$-${\\rm to}$-${\\rm Total\\ M_{\\ast}\\ Ratio}$')\n", + "\n", + "figname = 'cam_example_bt_distributions.png'\n", + "fig.savefig(figname, bbox_extra_artists=[xlabel, ylabel], bbox_inches='tight')" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "xmin, xmax = 9, 11.25\n", + "\n", + "fig, ax = plt.subplots(1, 1)\n", + "xscale = ax.set_xscale('log')\n", + "\n", + "from scipy.stats import binned_statistic\n", + "log_mass_bins = np.linspace(xmin, xmax, 25)\n", + "mass_mids = 10**(0.5*(log_mass_bins[:-1] + log_mass_bins[1:]))\n", + "\n", + "median_bt, __, __ = binned_statistic(\n", + " halocat.halo_table['stellar_mass'], halocat.halo_table['bulge_to_total_ratio'], \n", + " bins=10**log_mass_bins, statistic='median')\n", + "std_bt, __, __ = binned_statistic(\n", + " halocat.halo_table['stellar_mass'], halocat.halo_table['bulge_to_total_ratio'], \n", + " bins=10**log_mass_bins, statistic=np.std)\n", + "\n", + "low_spin_mask = halocat.halo_table['spin_percentile'] < 0.5\n", + "median_bt_low_spin, __, __ = binned_statistic(\n", + " halocat.halo_table['stellar_mass'][low_spin_mask], \n", + " halocat.halo_table['bulge_to_total_ratio'][low_spin_mask], \n", + " bins=10**log_mass_bins, statistic='median')\n", + "std_bt_low_spin, __, __ = binned_statistic(\n", + " halocat.halo_table['stellar_mass'][low_spin_mask], \n", + " halocat.halo_table['bulge_to_total_ratio'][low_spin_mask], \n", + " bins=10**log_mass_bins, statistic=np.std)\n", + "\n", + "high_spin_mask = halocat.halo_table['spin_percentile'] > 0.5\n", + "median_bt_high_spin, __, __ = binned_statistic(\n", + " halocat.halo_table['stellar_mass'][high_spin_mask], \n", + " halocat.halo_table['bulge_to_total_ratio'][high_spin_mask], \n", + " bins=10**log_mass_bins, statistic='median')\n", + "std_bt_high_spin, __, __ = binned_statistic(\n", + " halocat.halo_table['stellar_mass'][high_spin_mask], \n", + " halocat.halo_table['bulge_to_total_ratio'][high_spin_mask], \n", + " bins=10**log_mass_bins, statistic=np.std)\n", + "\n", + "y1 = median_bt_low_spin - std_bt_low_spin\n", + "y2 = median_bt_low_spin + std_bt_low_spin\n", + "__=ax.fill_between(mass_mids, y1, y2, alpha=0.8, color='red', \n", + " label=r'${\\rm low\\ spin\\ halos}$')\n", + "\n", + "y1 = median_bt_high_spin - std_bt_high_spin\n", + "y2 = median_bt_high_spin + std_bt_high_spin\n", + "__=ax.fill_between(mass_mids, y1, y2, alpha=0.8, color='blue',\n", + " label=r'${\\rm high\\ spin\\ halos}$')\n", + "\n", + "ylim = ax.set_ylim(0, 1)\n", + "\n", + "legend = ax.legend(loc='upper left')\n", + "\n", + "xlabel = ax.set_xlabel(r'${\\rm M_{\\ast}/M_{\\odot}}$')\n", + "ylabel = ax.set_ylabel(r'$\\langle{\\rm B/T}\\rangle$')\n", + "\n", + "figname = 'cam_example_bulge_disk_ratio.png'\n", + "fig.savefig(figname, bbox_extra_artists=[xlabel, ylabel], bbox_inches='tight')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda root]", + "language": "python", + "name": "conda-root-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/quickstart_and_tutorials/index.rst b/docs/quickstart_and_tutorials/index.rst index 19ebdc66d..a36d39545 100644 --- a/docs/quickstart_and_tutorials/index.rst +++ b/docs/quickstart_and_tutorials/index.rst @@ -49,7 +49,7 @@ Galaxy-Halo Modeling tutorials/model_building/preloaded_models/index tutorials/model_building/index ../source_notes/empirical_models/factories/index - ../quickstart_and_tutorials/tutorials/model_building/cam_tutorial + ../quickstart_and_tutorials/tutorials/model_building/cam_tutorial_pages/cam_tutorial Galaxy/Halo Catalog Analysis --------------------------------------------------------- diff --git a/docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial_pages/cam_complex_sfr.rst b/docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial_pages/cam_complex_sfr.rst new file mode 100644 index 000000000..eee8c1f95 --- /dev/null +++ b/docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial_pages/cam_complex_sfr.rst @@ -0,0 +1,95 @@ +.. _cam_complex_sfr: + +Modeling Complex Star-Formation Rates +============================================== + +In this example, we will show how to use Conditional Abundance Matching to model +a correlation between the mass accretion rate of a halo and the specific +star-formation rate of the galaxy living in the halo. +The code used to generate these results can be found here: + + **halotools/docs/notebooks/cam_modeling/cam_complex_sfr_tutorial.ipynb** + +Observed star-formation rate distribution +------------------------------------------ + +We will work with a distribution of star-formation +rates that would be difficult to model analytically, but that is well-sampled +by some observed galaxy population. The particular form of this distribution +is not important for this tutorial, since our CAM application will directly +use the "observed" population to define the distribution that we recover. + +.. image:: /_static/cam_example_complex_sfr.png + +The plot above shows the specific star-formation rates of the +toy galaxy distribution we have created for demonstration purposes. +Briefly, there are separate distributions for quenched and star-forming galaxies. +For the quenched galaxies, we model sSFR using an exponential power law; +for star-forming galaxies, we use a log-normal; +implementation details can be found in the notebook. + + +Modeling sSFR with CAM +------------------------------------------ + +We will start out by painting stellar mass onto subhalos +in the Bolshoi simulation, which we do using +the stellar-to-halo mass relation from Moster et al 2013. + +.. code:: python + + from halotools.sim_manager import CachedHaloCatalog + halocat = CachedHaloCatalog() + + from halotools.empirical_models import Moster13SmHm + model = Moster13SmHm() + + halocat.halo_table['stellar_mass'] = model.mc_stellar_mass( + prim_haloprop=halocat.halo_table['halo_mpeak'], redshift=0) + + +Algorithm description +~~~~~~~~~~~~~~~~~~~~~~ + +We will now use CAM to paint star-formation rates onto these model galaxies. +The way the algorithm works is as follows. For every model galaxy, +we find the observed galaxy with the closest stellar mass. +We set up a window of ~200 observed galaxies bracketing this matching galaxy; +this window defines :math:`{\rm Prob(< sSFR | M_{\ast})}`, which allows us to +calculate the rank-order sSFR-percentile for each galaxy in the window. +Similarly, we set up a window of ~200 model galaxies; this window +defines :math:`{\rm Prob(< dM_{vir}/dt | M_{\ast})}`, which allows us to +calculate the rank-order accretion-rate-percentile of our model galaxy, +:math:`r_1`. Then we simply search the observed window for the +observed galaxy whose rank-order sSFR-percentile equals +:math:`r_1`, and map its sSFR value onto our model galaxy. +We perform that calculation for every model galaxy with the following syntax: + +.. code:: python + + from halotools.empirical_models import conditional_abunmatch + x = halocat.halo_table['stellar_mass'] + y = halocat.halo_table['halo_dmvir_dt_100myr'] + x2 = galaxy_mstar + y2 = np.log10(galaxy_ssfr) + nwin = 201 + halocat.halo_table['log_ssfr'] = conditional_abunmatch(x, y, x2, y2, nwin) + + +Results +~~~~~~~~~~~~~~~~~~~~~~ + +Now let's inspect the results of our calculation. First we show that the +distribution specific star-formation rates of our model galaxies +matches the observed distribution across the range of stellar mass: + + +.. image:: /_static/cam_example_complex_sfr_recovery.png + +Next we can see that these sSFR values are tightly correlated +with halo accretion rate at fixed stellar mass: + +.. image:: /_static/cam_example_complex_sfr_dmdt_correlation.png + + + diff --git a/docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial_pages/cam_decorated_clf.rst b/docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial_pages/cam_decorated_clf.rst new file mode 100644 index 000000000..d6b2e9266 --- /dev/null +++ b/docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial_pages/cam_decorated_clf.rst @@ -0,0 +1,92 @@ +.. _cam_decorated_clf: + + +Modeling Central Galaxy Luminosity with Assembly Bias +========================================================================== + +In this example, we will show how to use Conditional Abundance Matching to +map central galaxy luminosity onto halos in a way that simultaneously correlates +with halo :math:`M_{\rm vir}` and halo :math:`V_{\rm max}`. +The code used to generate these results can be found here: + + **halotools/docs/notebooks/cam_modeling/cam_decorated_clf.ipynb** + + +Baseline mass-to-light model +------------------------------------------ + +The approach we will demonstrate in this tutorial is very similar to the ordinary +Conditional Luminosity Function model (CLF) of central galaxy luminosity. +In the CLF, a parameterized form is chosen for the median luminosity +of central galaxies as a function of host halo mass. The code below +shows how to calculate this median luminosity for every host halo in the Bolshoi simulation: + +.. code:: python + + from halotools.sim_manager import CachedHaloCatalog + halocat = CachedHaloCatalog(simname='bolplanck') + host_halos = halocat.halo_table[halocat.halo_table['halo_upid']==-1] + from halotools.empirical_models import Cacciato09Cens + model = Cacciato09Cens() + host_halos['median_luminosity'] = model.median_prim_galprop( + prim_haloprop=host_halos['halo_mvir']) + +To generate a Monte Carlo realization of the model, +one typically assumes that luminosities are distributed +as a log-normal distribution centered about this median relation. +While there is already a convenience function +`~halotools.empirical_models.Cacciato09Cens.mc_prim_galprop` for the +`~halotools.empirical_models.Cacciato09Cens` class that handles this, +it is straightforward to do this yourself +using the `~scipy.stats.norm` function in `scipy.stats`. +You just need to generate uniform random numbers and pass the result to the +`scipy.stats.norm.isf` function: + +.. code:: python + + from scipy.stats import norm + loc = np.log10(host_halos['median_luminosity']) + uran = np.random.rand(len(host_halos)) + host_halos['luminosity'] = 10**norm.isf(1-uran, loc=loc, scale=0.2) + +The *isf* function analytically evaluates the inverse CDF of the normal distribution, +and so this Monte Carlo method is based on inverse transformation sampling. +It is probably more common to use `numpy.random.normal` for this purpose, +but doing things with `scipy.stats.norm` will make it easier +to see how CAM works in the next section. + + +Correlating scatter in luminosity with halo :math:`V_{\rm max}` +---------------------------------------------------------------- + +As described in :ref:`cam_basic_idea`, we can generalize the inverse transformation sampling +technique so that the modeled variable is not purely stochastic, but is instead +correlated with some other variable. In this example, we will choose to +correlate the scatter with :math:`V_{\rm max}`. To do so, we need to calculate +:math:`{\rm Prob}(`_ model satellite +quenching with a simple analytical function :math:`{\rm Prob(\ quenched}\ \vert\ M_{\rm host})`, +where :math:`M_{\rm host}` is the dark matter mass of the satellite's parent halo. +For a standard implementation of this model, you can draw from a random uniform number generator +of the unit interval, and evaluate whether those draws are above or below :math:`{\rm Prob(\ quenched)}`. + +Alternatively, to implement CAM you would compute +:math:`p={\rm Prob(< r/R_{vir}}\ \vert\ M_{\rm host})` for each simulated subhalo, +and then evaluate whether each :math:`p` +is above or below :math:`{\rm Prob(\ quenched}\ \vert\ M_{\rm host})`. +This technique lets you generate a series of mocks with exactly the same +:math:`{\rm Prob(\ quenched}\ \vert\ M_{\rm host})`, +but with tunable levels of quenching gradient, ranging from zero gradient +to the statistical extrema. +The `~halotools.utils.sliding_conditional_percentile` function can be used to +calculate :math:`p={\rm Prob(< r/R_{vir}}\ \vert\ M_{\rm host}).` + + +The plot below demonstrates three different mock catalogs made with CAM in this way. +The left hand plot shows how the quenched fraction of satellites varies +with intra-halo position. The right hand plot confirms that all three mocks have +statistically indistinguishable "halo mass quenching", even though their gradients +are very different. + +.. image:: /_static/quenching_gradient_models.png + +The next plot compares the 3d clustering between these models. + +.. image:: /_static/quenching_gradient_model_clustering.png + +For implementation details, the code producing these plots +can be found in the following Jupyter notebook: + + **halotools/docs/notebooks/galcat_analysis/intermediate_examples/quenching_gradient_tutorial.ipynb** + + + + + diff --git a/docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial_pages/cam_tutorial.rst b/docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial_pages/cam_tutorial.rst new file mode 100644 index 000000000..b0303794f --- /dev/null +++ b/docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial_pages/cam_tutorial.rst @@ -0,0 +1,151 @@ +.. _cam_tutorial: + +********************************************************************** +Tutorial on Conditional Abundance Matching +********************************************************************** + +Conditional Abundance Matching (CAM) is a technique that you can use to +model a variety of correlations between galaxy and halo properties, +such as the dependence of galaxy quenching upon both halo mass and +halo formation time, or the dependence of galaxy disk size upon halo spin. +This tutorial explains CAM by applying the technique to a few different problems. + + +.. _cam_basic_idea: + +Basic Idea Behind CAM +====================== + +CAM is designed to answer questions of the following form: +*does halo property A correlate with galaxy property B?* +The Halotools approach to answering such questions is via forward modeling: +a mock universe is created in which the A--B correlation exists; +comparing the mock universe to the real one allows you to evaluate the +success of the A--B correlation hypothesis. + +Forward-modeling the galaxy-halo connection requires specifying +some statistical distribution of the galaxy property being modeled, +so that Monte Carlo realizations can be drawn from the distribution. +CAM uses the most ubiquitous approach to generating Monte Carlo realizations, +*inverse transformation sampling,* in which the statistical distribution +is specified in terms of the cumulative distribution function (CDF), +:math:`{\rm CDF}(z) \equiv {\rm Prob}(< z).` +Briefly, the way this work is that once you specify the CDF, +you only need to generate a realization of a random uniform distribution, +and pass the values of that realization to the CDF inverse, :math:`{\rm CDF}^{-1}(p),` +which evaluates to the variable :math:`z` being painted on the model galaxies. +See the `Transformation of Probability tutorial `_ +for pedagogical derivations associated with inverse transformation sampling, +and the `~halotools.utils.monte_carlo_from_cdf_lookup` function +for a convenient one-liner syntax. + +In ordinary applications of inverse transformation sampling, +the use of a random uniform variable guarantees +that the output variables :math:`z` will be distributed according to +:math:`{\rm Prob}(z),` and that each individual :math:`z` will be purely stochastic. +CAM generalizes this common technique so that :math:`{\rm Prob}(z)` +is still recovered exactly, and moreover :math:`z` exhibits residual correlations +with some other variable, :math:`h`. Operationally, the way this works is that +rather than evaluating :math:`{\rm CDF}^{-1}(p)` with random uniform variables, +instead you evaluate with :math:`p = {\rm CDF}(h) = {\rm Prob}(< h),` +introducing a monotonic correlation between :math:`z` and :math:`h`. +In most applications, :math:`h` is some halo property like mass accretion rate, +and :math:`z` is some galaxy property like star-formation rate. +In this way, the galaxy property you paint on to your halos will +trace the distribution :math:`{\rm Prob}(z)`, such that above-average +values of :math:`z` will be painted onto halos with above average values of +:math:`h`, and conversely. + +Finally, the "Conditional" part of CAM is that this technique naturally generalizes to +introduce a galaxy property correlation while holding some other property fixed. +For example, at fixed stellar mass, it is natural to hypothesize that +star-forming galaxies live in halos that are rapidly accreting mass, +and that quiescent galaxies live in halos that have already built up most of their mass. +In this kind of CAM application, we have: +:math:`{\rm Prob}(z)\rightarrow{\rm Prob}(`_ +for a fast and well-written code written by Yao-Yuan Mao +that provides a python wrapper around the deconvolution kernel written by Peter Behroozi. diff --git a/docs/whats_new_history/whats_new_0.7.rst b/docs/whats_new_history/whats_new_0.7.rst index 6a2430b66..21c862b97 100644 --- a/docs/whats_new_history/whats_new_0.7.rst +++ b/docs/whats_new_history/whats_new_0.7.rst @@ -38,3 +38,10 @@ New Mock Observables Inertia Tensor calculation ------------------------------- The pairwise calculation `~halotools.mock_observables.inertia_tensor_per_object` computes the inertia tensor of a mass distribution surrounding each point in a sample of galaxies or halos. + +API Changes +=========== + +* The old implementation of the `~halotools.empirical_models.conditional_abunmatch` function has been renamed to be `~halotools.empirical_models.conditional_abunmatch_bin_based`. + +* There is an entirely distinct, bin-free implementation of Conditional Abundance Matching that now bears the name `~halotools.empirical_models.conditional_abunmatch`. From 14c18a818b18c0053fb799169ff2882020f92703 Mon Sep 17 00:00:00 2001 From: Andrew Hearin Date: Mon, 12 Mar 2018 15:30:13 -0600 Subject: [PATCH 3/5] Updated utils module with cross-links to new functions --- CHANGES.rst | 2 ++ halotools/utils/conditional_percentile.py | 3 +-- halotools/utils/inverse_transformation_sampling.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 71714cdef..a85ddb83c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -15,6 +15,8 @@ - Renamed old implementation of `conditional_abunmatch` to `conditional_abunmatch_bin_based` +- Added new bin-free implementation of `conditional_abunmatch`. + 0.6 (2017-12-15) ---------------- diff --git a/halotools/utils/conditional_percentile.py b/halotools/utils/conditional_percentile.py index 4657b5e59..dd2b9f15d 100644 --- a/halotools/utils/conditional_percentile.py +++ b/halotools/utils/conditional_percentile.py @@ -13,8 +13,7 @@ def sliding_conditional_percentile(x, y, window_length, assume_x_is_sorted=False, add_subgrid_noise=True, seed=None): - r""" Estimate the conditional cumulative distribution function Prob(< y | x) - using a sliding window of length ``window_length``. + r""" Estimate the cumulative distribution function Prob(< y | x). Parameters ---------- diff --git a/halotools/utils/inverse_transformation_sampling.py b/halotools/utils/inverse_transformation_sampling.py index b48148d1d..1868fc70d 100644 --- a/halotools/utils/inverse_transformation_sampling.py +++ b/halotools/utils/inverse_transformation_sampling.py @@ -54,7 +54,7 @@ def monte_carlo_from_cdf_lookup(x_table, y_table, mc_input='random', Notes ----- - See the Transformation of Probability tutorial at https://github.com/jbailinua/probability + See the `Transformation of Probability tutorial `_ for pedagogical derivations associated with inverse transformation sampling. Examples From df4dc4dba4d4b6f15fcb18975d1c03fa419e9d28 Mon Sep 17 00:00:00 2001 From: Andrew Hearin Date: Mon, 12 Mar 2018 15:31:07 -0600 Subject: [PATCH 4/5] Removed obsolete tutorial --- .../tutorials/model_building/cam_tutorial.rst | 102 ------------------ 1 file changed, 102 deletions(-) delete mode 100644 docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial.rst diff --git a/docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial.rst b/docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial.rst deleted file mode 100644 index b1fb41bfa..000000000 --- a/docs/quickstart_and_tutorials/tutorials/model_building/cam_tutorial.rst +++ /dev/null @@ -1,102 +0,0 @@ -:orphan: - -.. _cam_tutorial: - -********************************************************************** -Tutorial on Conditional Abundance Matching -********************************************************************** - -Conditional Abundance Matching (CAM) is a technique that you can use to -model a variety of correlations between galaxy and halo properties, -such as the dependence of galaxy quenching upon both halo mass and -halo formation time. This tutorial explains CAM by applying -the technique to a few different problems. -Each of the following worked examples are independent from one another, -and illustrate the range of applications of the technique. - - -Basic Idea -================= - -Forward-modeling the galaxy-halo connection requires specifying -some statistical distribution of the galaxy property being modeled, -so that Monte Carlo realizations can be drawn from the distribution. -The most convenient distribution to use for this purpose is the cumulative -distribution function (CDF), :math:`{\rm CDF}(x) = {\rm Prob}(< x).` -Once the CDF is specified, you only need to generate -a realization of a random uniform distribution and pass those draws to the -CDF inverse, :math:`{\rm CDF}^{-1}(p),` which evaluates to the variable -:math:`x` being painted on the model galaxies. - -CAM introduces correlations between the -galaxy property :math:`x` and some halo property :math:`h,` -without changing :math:`{\rm CDF}(x)`. Rather than evaluating :math:`{\rm CDF}^{-1}(p)` -with random uniform variables, -instead you evaluate with :math:`p = {\rm CDF}(h) = {\rm Prob}(< h),` -introducing a monotonic correlation between :math:`x` and :math:`h`. - -The function `~halotools.empirical_models.noisy_percentile` can be used to -add controllable levels of noise to :math:`p = {\rm CDF}(h).` -This allows you to control the correlation coefficient -between :math:`x` and :math:`h,` -always exactly preserving the 1-point statistics of the output distribution. - - -The "Conditional" part of CAM is that this technique naturally generalizes to -introduce a galaxy property correlation while holding some other property fixed. -Age Matching in `Hearin and Watson 2013 `_ -is an example of this: the distribution :math:`{\rm Prob}(`_ model satellite -quenching with a simple analytical function :math:`{\rm Prob(\ quenched}\ \vert\ M_{\rm host})`, -where :math:`M_{\rm host}` is the dark matter mass of the satellite's parent halo. -For a standard implementation of this model, you can draw from a random uniform number generator -of the unit interval, and evaluate whether those draws are above or below :math:`{\rm Prob(\ quenched)}`. - -Alternatively, to implement CAM you would compute -:math:`p={\rm Prob(< r/R_{vir}}\ \vert\ M_{\rm host})` for each simulated subhalo, -and then evaluate whether each :math:`p` -is above or below :math:`{\rm Prob(\ quenched}\ \vert\ M_{\rm host})`. -This technique lets you generate a series of mocks with exactly the same -:math:`{\rm Prob(\ quenched}\ \vert\ M_{\rm host})`, -but with tunable levels of quenching gradient, ranging from zero gradient -to the statistical extrema. -The `~halotools.utils.sliding_conditional_percentile` function can be used to -calculate :math:`p={\rm Prob(< r/R_{vir}}\ \vert\ M_{\rm host}).` - - -The plot below demonstrates three different mock catalogs made with CAM in this way. -The left hand plot shows how the quenched fraction of satellites varies -with intra-halo position. The right hand plot confirms that all three mocks have -statistically indistinguishable "halo mass quenching", even though their gradients -are very different. - -.. image:: /_static/quenching_gradient_models.png - -The next plot compares the 3d clustering between these models. - -.. image:: /_static/quenching_gradient_model_clustering.png - -For implementation details, the code producing these plots -can be found in the following Jupyter notebook: - - **halotools/docs/notebooks/galcat_analysis/intermediate_examples/quenching_gradient_tutorial.ipynb** - - - - - From fe21a9f15d15bc8bb4eebc5a47f52856c8154240 Mon Sep 17 00:00:00 2001 From: Andrew Hearin Date: Mon, 12 Mar 2018 16:43:39 -0600 Subject: [PATCH 5/5] Fixed bugs in test suite leading to Travis failures in certain environments --- .../empirical_models/abunmatch/tests/test_bin_free_cam.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/halotools/empirical_models/abunmatch/tests/test_bin_free_cam.py b/halotools/empirical_models/abunmatch/tests/test_bin_free_cam.py index 0a5fd05a0..76ec2f637 100644 --- a/halotools/empirical_models/abunmatch/tests/test_bin_free_cam.py +++ b/halotools/empirical_models/abunmatch/tests/test_bin_free_cam.py @@ -158,7 +158,7 @@ def test_brute_force_interior_points(): num_tests = 50 nwin = 11 - nhalfwin = nwin/2 + nhalfwin = int(nwin/2) for i in range(num_tests): seed = fixed_seed + i @@ -193,7 +193,7 @@ def test_brute_force_left_endpoints(): num_tests = 50 nwin = 11 - nhalfwin = nwin/2 + nhalfwin = int(nwin/2) for i in range(num_tests): seed = fixed_seed + i @@ -228,7 +228,7 @@ def test_brute_force_right_points(): num_tests = 50 nwin = 11 - nhalfwin = nwin/2 + nhalfwin = int(nwin/2) for i in range(num_tests): seed = fixed_seed + i