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)
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)