Beispiel #1
0
    def __init__(self, info_params, allow_renames=True, ignore_unused_sampled=False):
        self.set_logger(lowercase=True)
        self.allow_renames = allow_renames
        # First, we load the parameters,
        # not caring about whether they are understood by any likelihood.
        # `input` contains the parameters (expected to be) understood by the likelihood,
        #   with its fixed value, its fixing function, or None if their value is given
        #   directly by the sampler.
        self._infos = {}
        self._input = {}
        self._input_funcs = {}
        self._input_args = {}
        self._output = {}
        self._constant = {}
        self._sampled = {}
        self._sampled_renames = {}
        self._derived = {}
        self._derived_funcs = {}
        self._derived_args = {}
        # Notice here that expand_info_param *always* adds a partag.derived:True tag
        # to infos without _prior or partag.value, and a partag.value field
        # to fixed params
        for p, info in info_params.items():
            self._infos[p] = deepcopy_where_possible(info)
            if is_fixed_param(info):
                if isinstance(info[partag.value], Number):
                    self._constant[p] = info[partag.value]
                    if not info.get(partag.drop, False):
                        self._input[p] = self._constant[p]
                else:
                    self._input[p] = None
                    self._input_funcs[p] = get_external_function(info[partag.value])
                    self._input_args[p] = getfullargspec(self._input_funcs[p]).args
            if is_sampled_param(info):
                self._sampled[p] = None
                if not info.get(partag.drop, False):
                    self._input[p] = None
                self._sampled_renames[p] = (
                    (lambda x: [x] if isinstance(x, str) else x)
                    (info.get(partag.renames, [])))
            if is_derived_param(info):
                self._derived[p] = deepcopy_where_possible(info)
                # Dynamical parameters whose value we want to save
                if info[partag.derived] is True and is_fixed_param(info):
                    info[partag.derived] = "lambda %s: %s" % (p, p)
                if info[partag.derived] is True:
                    self._output[p] = None
                else:
                    self._derived_funcs[p] = get_external_function(info[partag.derived])
                    self._derived_args[p] = getfullargspec(self._derived_funcs[p]).args
        # Check that the sampled and derived params are all valid python variable names
        for p in chain(self._sampled, self._derived):
            if not is_valid_variable_name(p):
                is_in = p in self._sampled
                eg_in = "  p_prime:\n    prior: ...\n  %s: 'lambda p_prime: p_prime'\n" % p
                eg_out = "  p_prime: 'lambda %s: %s'\n" % (p, p)
                raise LoggedError(
                    self.log, "Parameter name '%s' is not a valid Python variable name "
                              "(it needs to start with a letter or '_').\n"
                              "If this is an %s parameter of a likelihood or theory, "
                              "whose name you cannot change,%s define an associated "
                              "%s one with a valid name 'p_prime' as: \n\n%s",
                    p, "input" if is_in else "output",
                    "" if is_in else " remove it and",
                    "sampled" if is_in else "derived",
                    eg_in if is_in else eg_out)
        # Assume that the *un*known function arguments are likelihood/theory
        # output parameters
        for arg in (set(chain(*self._input_args.values()))
                            .union(chain(*self._derived_args.values()))
                    - set(self._constant) - set(self._input)
                    - set(self._sampled) - set(self._derived)):
            self._output[arg] = None

        # Useful sets: directly-sampled input parameters and directly "output-ed" derived
        self._directly_sampled = [p for p in self._input if p in self._sampled]
        self._directly_output = [p for p in self._derived if p in self._output]
        # Useful mapping: input params that vary if each sample is varied
        self._sampled_input_dependence = {s: [i for i in self._input
                                              if s in self._input_args.get(i, {})]
                                          for s in self._sampled}
        # From here on, some error control.
        dropped_but_never_used = (
            set(p for p, v in self._sampled_input_dependence.items() if not v)
                .difference(set(self._directly_sampled)))
        if dropped_but_never_used and not ignore_unused_sampled:
            raise LoggedError(
                self.log,
                "Parameters %r are sampled but not passed to a likelihood or theory "
                "code, and never used as arguments for any parameter functions. "
                "Check that you are not using the '%s' tag unintentionally.",
                list(dropped_but_never_used), partag.drop)
        # input params depend on input and sampled only, never on output/derived
        all_input_arguments = set(chain(*self._input_args.values()))
        bad_input_dependencies = all_input_arguments.difference(
            set(self.input_params()).union(set(self.sampled_params())).union(
                set(self.constant_params())))
        if bad_input_dependencies:
            raise LoggedError(
                self.log,
                "Input parameters defined as functions can only depend on other "
                "input parameters that are not defined as functions. "
                "In particular, an input parameter cannot depend on %r."
                "Use an explicit Theory calculator for more complex dependencies.",
                list(bad_input_dependencies))
        self._wrapped_input_funcs, self._wrapped_derived_funcs = \
            self._get_wrapped_functions_evaluation_order()
        # warn if repeated labels
        labels_inv_repeated = invert_dict(self.labels())
        for k in list(labels_inv_repeated):
            if len(labels_inv_repeated[k]) == 1:
                labels_inv_repeated.pop(k)
        if labels_inv_repeated:
            self.log.warn("There are repeated parameter labels: %r", labels_inv_repeated)
Beispiel #2
0
    def __init__(self,
                 info_params: Union[ParamsDict, ExpandedParamsDict],
                 allow_renames=True,
                 ignore_unused_sampled=False):
        self.set_logger()
        self.allow_renames = allow_renames
        # First, we load the parameters,
        # not caring about whether they are understood by any likelihood.
        # `input` contains the parameters (expected to be) understood by the likelihood,
        #   with its fixed value, its fixing function, or None if their value is given
        #   directly by the sampler.
        self._infos = {}
        self._input: ParamValuesDict = {}
        self._input_funcs = {}
        self._input_args = {}
        self._input_dependencies: Dict[str, Set[str]] = {}
        self._dropped: Set[str] = set()
        self._output: ParamValuesDict = {}
        self._constant: ParamValuesDict = {}
        self._sampled: ParamValuesDict = {}
        self._sampled_renames: Dict[str, List[str]] = {}
        self._derived: ParamValuesDict = {}
        self._derived_inputs = []
        self._derived_funcs = {}
        self._derived_args = {}
        self._derived_dependencies: Dict[str, Set[str]] = {}
        # Notice here that expand_info_param *always* adds a "derived":True tag
        # to infos without "prior" or "value", and a "value" field
        # to fixed params
        for p, info in info_params.items():
            if isinstance(info, Mapping) and not set(info).issubset(partags):
                raise LoggedError(self.log,
                                  "Parameter '%s' has unknown options %s", p,
                                  set(info).difference(partags))
            info = expand_info_param(info)
            self._infos[p] = info
            if is_fixed_or_function_param(info):
                if isinstance(info["value"], Real):
                    self._constant[p] = float(info["value"])
                    self._input[p] = self._constant[p]
                    if info.get("drop"):
                        self._dropped.add(p)
                else:
                    self._input[p] = np.nan
                    self._input_funcs[p] = get_external_function(info["value"])
                    self._input_args[p] = getfullargspec(
                        self._input_funcs[p]).args
            if is_sampled_param(info):
                self._sampled[p] = np.nan
                self._input[p] = np.nan
                if info.get("drop"):
                    self._dropped.add(p)
                self._sampled_renames[p] = str_to_list(
                    info.get("renames") or [])
            if is_derived_param(info):
                self._derived[p] = np.nan
                # Dynamical parameters whose value we want to save
                if info["derived"] is True and is_fixed_or_function_param(
                        info):
                    # parameters that are already known or computed by input funcs
                    self._derived_inputs.append(p)
                elif info["derived"] is True:
                    self._output[p] = np.nan
                else:
                    self._derived_funcs[p] = get_external_function(
                        info["derived"])
                    self._derived_args[p] = getfullargspec(
                        self._derived_funcs[p]).args
        # Check that the sampled and derived params are all valid python variable names
        for p in chain(self._sampled, self._derived):
            if not is_valid_variable_name(p):
                is_in = p in self._sampled
                eg_in = "  p_prime:\n    prior: ...\n  %s: " \
                        "'lambda p_prime: p_prime'\n" % p
                eg_out = "  p_prime: 'lambda %s: %s'\n" % (p, p)
                raise LoggedError(
                    self.log,
                    "Parameter name '%s' is not a valid Python variable name "
                    "(it needs to start with a letter or '_').\n"
                    "If this is an %s parameter of a likelihood or theory, "
                    "whose name you cannot change,%s define an associated "
                    "%s one with a valid name 'p_prime' as: \n\n%s", p,
                    "input" if is_in else "output",
                    "" if is_in else " remove it and",
                    "sampled" if is_in else "derived",
                    eg_in if is_in else eg_out)

        # input params depend on input and sampled only,
        # never on output/derived unless constant
        known_input = set(self._input)
        all_input_arguments = set(chain(*self._input_args.values()))
        bad_input_dependencies = all_input_arguments - known_input
        if bad_input_dependencies:
            raise LoggedError(
                self.log,
                "Input parameters defined as functions can only depend on other "
                "input parameters. In particular, an input parameter cannot depend on %r."
                " Use an explicit Theory calculator for more complex dependencies.\n"
                "If you intended to define a derived output parameter use derived: "
                "instead of value:", list(bad_input_dependencies))

        # Assume that the *un*known function arguments are likelihood/theory
        # output parameters
        for arg in (all_input_arguments.union(*self._derived_args.values()).
                    difference(known_input).difference(self._derived)):
            self._output[arg] = np.nan

        # Useful set: directly "output-ed" derived
        self._directly_output = [p for p in self._derived if p in self._output]

        self._wrapped_input_funcs, self._wrapped_derived_funcs = \
            self._get_wrapped_functions_evaluation_order()

        # Useful mapping: input params that vary if each sample is varied
        self._sampled_input_dependence = {
            s: [
                i for i in self._input
                if s in self._input_dependencies.get(i, {})
            ]
            for s in self._sampled
        }
        # From here on, some error control.
        # Only actually raise error after checking if used by prior.
        if not ignore_unused_sampled:
            self._dropped_not_directly_used = self._dropped.intersection(
                p for p, v in self._sampled_input_dependence.items() if not v)
        else:
            self._dropped_not_directly_used = set()

        # warn if repeated labels
        labels_inv_repeated = invert_dict(self.labels())
        labels_inv_repeated = {
            k: v
            for k, v in labels_inv_repeated.items() if len(v) > 1
        }
        if labels_inv_repeated:
            self.log.warning("There are repeated parameter labels: %r",
                             labels_inv_repeated)