Source code for hmf.helpers.functional

"""
This module provides functions for generating several :class:`hmf.hmf.MassFunction` instances
from a combination of lists of parameters, in optimal order.

The underlying idea here is that typically modifying say the redshift has a smaller number
of re-computations than modifying the base cosmological parameters. Thus, in a nested
loop, the redshift should be the inner loop.

It is not always obvious which order the loops should be, so this module provides
functions to determine that order, and indeed perform the loops.
"""

import collections
from ..mass_function import hmf
import itertools


[docs]def get_best_param_order(kls, q="dndm", **kwargs): """ Get an optimal parameter order for nested loops. The underlying idea here is that typically modifying say the redshift has a smaller number of re-computations than modifying the base cosmological parameters. Thus, in a nested loop, the redshift should be the inner loop. It is not always obvious which order the loops should be, so this function determines that order. .. note :: The order is calculated based on actually running one iteration of the loop, so providing arguments which enable a fast calculation is helpful. Parameters ---------- kls : :class:`hmf._framwork.Framework` class An arbitrary framework for which to determine parameter ordering. q : str or list of str A string specifying the desired output (e.g. ``"dndm"``), or a list of such strings. kwargs : unpacked-dict Arbitrary keyword arguments to the framework initialiser. These are only for determination of parameter order and so may be very poor in resolution to improve efficiency. Returns ------- final_list : list An ordered list of parameters, with the first corresponding to the outer-most loop. Examples -------- >>> from src.hmf import MassFunction >>> print get_best_param_order(MassFunction,"dndm",transfer_model="BBKS",dlnk=1,dlog10m=1)[::3] ['z2', 'hmf_model', 'delta_wrt', 'growth_params', 'filter_params', 'Mmin', 'transfer_params', 'dlnk', 'cosmo_params'] """ a = kls(**kwargs) if isinstance(q, str): getattr(a, q) else: for qq in q: getattr(a, qq) final_list = [] final_num = [] for k, v in getattr(a, "_" + a.__class__.__name__ + "__recalc_par_prop").items(): num = len(v) for i, ln in enumerate(final_num): if ln >= num: break else: final_list += [k] final_num += [num] continue final_list.insert(i, k) final_num.insert(i, num) return final_list[::-1]
[docs]def get_hmf( req_qauntities, get_label=True, framework=hmf.MassFunction, fast_kwargs={ "transfer_model": "BBKS", "lnk_min": -1, "lnk_max": 1, "dlnk": 1, "Mmin": 10, "Mmax": 11.5, "dlog10m": 0.5, }, label_kind="display", label_kwargs=None, **kwargs, ): """ Yield framework instances for all combinations of parameters supplied. The underlying idea here is that typically modifying say the redshift has a smaller number of re-computations than modifying the base cosmological parameters. Thus, in a nested loop, the redshift should be the inner loop. It is not always obvious which order the loops should be, but this function internally determines the order, and calculates the requisite quantities in a series of framework instances. Parameters ---------- req_qauntities : str or list of str A string defining the quantities that should be pre-cached in the output instances. It is advisable that *any* required quantities for a given application be provided here, to ensure proper optimization. get_label : bool, optional Whether to return a list of string labels designating each combination of parameters. framework : :class:`hmf._framework.Framework` class, optional A framework for which to perform the optimization. fast_kwargs : dict, optional Parameters to be used in the initial run to determine optimal order. These should be set to provide very quick calculation, and do not affect the final result. This will need to be over-ridden for frameworks other than :class:`hmf.MassFunction`. kwargs : unpacked-dict Any of the parameters to the initialiser of `framework` which should be calculated. These may be scalar or lists. The total number of calculations will be the total combination of all parameters. Yields ------ quantities : list A list of quantities, specified by the `req_quantities` arguments x : Framework instance An instance of `framework`, with the requisite quantities pre-cached. label : optional If `get_label` is True, also returns a string label uniquely specifying the current parameter combination. Examples -------- The following operation will run 12 iterations, yielding the desired quantities, an instance containing those and other quantities, and a unique label at every iteration. >>> for quants, mf, label in get_hmf(['dndm','ngtm'],z=range(3),hmf_model=["ST","PS"],sigma_8=[0.7,0.8]): >>> print label sigma.8: 0.7, ST, z: 0 sigma.8: 0.7, PS, z: 0 sigma.8: 0.7, ST, z: 1 sigma.8: 0.7, PS, z: 1 sigma.8: 0.7, ST, z: 2 sigma.8: 0.7, PS, z: 2 sigma.8: 0.8, ST, z: 0 sigma.8: 0.8, PS, z: 0 sigma.8: 0.8, ST, z: 1 sigma.8: 0.8, PS, z: 1 sigma.8: 0.8, ST, z: 2 sigma.8: 0.8, PS, z: 2 To calculate all of them and keep the results as a list: >>> big_list = list(get_hmf('mean_density',z=range(8))) >>> print [x[0][0]/1e10 for x in big_list] [8.531878308131338, 68.2550264650507, 230.36071431954613, 546.0402117204056, 1066.4847885164174, 1842.885714556369, 2926.434259689049, 4368.321693763245] """ label_kwargs = label_kwargs or {} if isinstance(req_qauntities, str): req_qauntities = [req_qauntities] lists = {} for k, v in list(kwargs.items()): if isinstance(v, (list, tuple)): if len(v) > 1: lists[k] = kwargs.pop(k) else: kwargs[k] = v[0] x = framework(**kwargs) if not lists: if get_label: yield [[getattr(x, a) for a in req_qauntities], x, ""] else: yield [[getattr(x, a) for a in req_qauntities], x] if len(lists) == 1: for k, v in lists.items(): for vv in v: x.update(**{k: vv}) if get_label: yield [getattr(x, a) for a in req_qauntities], x, _make_label( {k: vv}, kind=label_kind, **label_kwargs ) else: yield [getattr(x, a) for a in req_qauntities], x elif len(lists) > 1: # should be really fast. order = get_best_param_order(framework, req_qauntities, **fast_kwargs) ordered_kwargs = collections.OrderedDict([]) for item in order: try: if isinstance(lists[item], (list, tuple)): ordered_kwargs[item] = lists.pop(item) except KeyError: pass # add the rest in any order (there shouldn't actually be any) for k in list(lists.items()): if isinstance(lists[k], (list, tuple)): ordered_kwargs[k] = lists.pop(k) ordered_list = [ordered_kwargs[k] for k in ordered_kwargs] final_list = [ collections.OrderedDict(list(zip(list(ordered_kwargs.keys()), v))) for v in itertools.product(*ordered_list) ] for vals in final_list: x.update(**vals) if not get_label: yield [[getattr(x, q) for q in req_qauntities], x] else: yield [ [getattr(x, q) for q in req_qauntities], x, _make_label(vals, kind=label_kind, **label_kwargs), ]
def _make_label(d, no_spaces=None, equals=None, delim=None, kind="display"): if kind == "display": space = " " if not no_spaces else "" equals = f":{space}" if equals is None else equals delim = f",{space}" if delim is None else delim elif kind == "filename": space = "" equals = "=" if equals is None else equals delim = "_" if delim is None else delim label = "" for key, val in d.items(): if isinstance(val, str): label += f"{val}{delim}" elif isinstance(val, dict): for k, v in val.items(): label += f"{k}{equals}{v}{delim}" else: label += f"{key}{equals}{val}{delim}" # Some post-formatting to make it look nicer label = label[: -len(delim)] if no_spaces: label = label.replace(" ", "") return label