From 293c4816836b1bc2e3d1c8a37222e815d9167aa0 Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Tue, 20 Aug 2024 19:58:47 +0200 Subject: [PATCH 01/21] Implemented UltraNest to example_16, double checked until run_fit() --- examples/example_16/ob08092-o4_MN.yaml | 1 + .../example_16/ob08092-o4_minimal_MN.yaml | 1 + examples/example_16/ulens_model_fit.py | 126 +++++++++++++++--- 3 files changed, 112 insertions(+), 16 deletions(-) diff --git a/examples/example_16/ob08092-o4_MN.yaml b/examples/example_16/ob08092-o4_MN.yaml index e15f5890c..aef222615 100644 --- a/examples/example_16/ob08092-o4_MN.yaml +++ b/examples/example_16/ob08092-o4_MN.yaml @@ -1,5 +1,6 @@ photometry_files: data/OB08092/phot_ob08092_O4.dat +fit_method: UltraNest # options: EMCEE, MultiNest, UltraNest (... dynesty) prior_limits: t_0: [2455379.4, 2455379.76] u_0: [0.3, 0.65] diff --git a/examples/example_16/ob08092-o4_minimal_MN.yaml b/examples/example_16/ob08092-o4_minimal_MN.yaml index 9a998a076..d83475640 100644 --- a/examples/example_16/ob08092-o4_minimal_MN.yaml +++ b/examples/example_16/ob08092-o4_minimal_MN.yaml @@ -1,5 +1,6 @@ photometry_files: data/OB08092/phot_ob08092_O4.dat +fit_method: UltraNest # options: EMCEE, MultiNest, UltraNest (... dynesty) prior_limits: t_0: [2455379.4, 2455379.76] u_0: [0.46, 0.65] diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index 3beb24950..9ad28d5a6 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -33,6 +33,10 @@ from pymultinest.analyse import Analyzer except Exception: import_failed.add("pymultinest") +try: + import ultranest +except Exception: + import_failed.add("ultranest") try: import MulensModel as mm @@ -69,6 +73,18 @@ class UlensModelFit(object): ``'scale_errorbars': {'factor': kappa, 'minimum': epsilon}`` to scale uncertainties. + fit_method: *str* + Method of fitting. Currently accepted values are ``EMCEE``, + ``MultiNest``, and ``UltraNest``. If not provided, the script + will guess it based on other parameters: ``EMCEE`` is selected + if ``starting_parameters`` are provided and ``MultiNest`` is + selected if ``prior_limits`` are provided. + + Webpage of each method: + - EMCEE: https://emcee.readthedocs.io/en/stable/ + - MultiNest: https://johannesbuchner.github.io/PyMultiNest/ + - UltraNest: https://johannesbuchner.github.io/UltraNest/ + starting_parameters: *dict* Starting values of the parameters. It also indicates the EMCEE fitting mode. @@ -108,12 +124,13 @@ class UlensModelFit(object): prior_limits: *dict* Upper and lower limits of parameters. - It also indicates the pyMultiNest fitting mode. + It only applies to pyMultiNest and UltraNest fitting. However, + if fit_method is not given, pyMultiNest will be applied. Keys are MulensModel parameters and values are lists of two floats each (alternatively a string giving 2 floats can be provided - see example below). Currently, no informative priors are allowed for - pyMultiNest fitting. Example input: + pyMultiNest and UltraNest fitting. Example input: .. code-block:: python @@ -157,7 +174,7 @@ class UlensModelFit(object): min_values: *dict* Minimum values of parameters that define the prior, e.g., ``{'t_E': 0.}``. Note that the these are only limits of a prior. - Functional form of priors can be defines in ``fit_constraints``. + Functional form of priors can be defined in ``fit_constraints``. It works only for EMCEE fitting. max_values: *dict* @@ -333,7 +350,7 @@ class UlensModelFit(object): """ def __init__( - self, photometry_files, + self, photometry_files, fit_method=None, starting_parameters=None, prior_limits=None, model=None, fixed_parameters=None, min_values=None, max_values=None, fitting_parameters=None, @@ -341,6 +358,7 @@ def __init__( ): self._check_MM_version() self._photometry_files = photometry_files + self._fit_method = fit_method self._starting_parameters_input = starting_parameters self._prior_limits = prior_limits self._model_parameters = model @@ -355,7 +373,10 @@ def __init__( self._which_task() self._set_default_parameters() if self._task == 'fit': - self._guess_fitting_method() + if self._fit_method is None: + self._guess_fitting_method() + else: + self._check_fitting_method() self._check_starting_parameters_type() self._set_fit_parameters_unsorted() self._check_imports() @@ -419,7 +440,7 @@ def _which_task(self): def _check_unnecessary_settings_plot(self): """ - Make sure that there arent' too many parameters specified for: + Make sure that there aren't too many parameters specified for: self._task == 'plot' """ keys = ['_starting_parameters_input', '_min_values', '_max_values', @@ -506,7 +527,7 @@ def _guess_fitting_method(self): "which makes impossible to choose the fitting method. " "These settings indicate EMCEE and pyMultiNest " "respectively, and cannot be both set.") - method = "MultiNest" + method = "MultiNest" # UltraNest is also supported if method is None: raise ValueError( "No fitting method chosen. You can chose either 'EMCEE' or " @@ -514,12 +535,27 @@ def _guess_fitting_method(self): "starting_parameters or prior_limits, respectively.") self._fit_method = method + def _check_fitting_method(self): + """ + Check if fitting method is consistent with the settings. + """ + if self._fit_method == "EMCEE": + if self._starting_parameters_input is None: + raise ValueError( + "EMCEE fitting method requires starting_parameters.") + elif self._fit_method in ["MultiNest", "UltraNest"]: + if self._prior_limits is None: + raise ValueError( + "pyMultiNest fitting method requires prior_limits.") + else: + raise ValueError("Invalid fitting method was inserted.") + def _check_starting_parameters_type(self): """ Check if starting parameters are read from file or will be drawn from distributions specified. """ - if self._fit_method == "MultiNest": + if self._fit_method in ["MultiNest", "UltraNest"]: return if 'file' in self._starting_parameters_input: @@ -547,7 +583,7 @@ def _set_fit_parameters_unsorted(self): else: raise ValueError( 'unexpected: ' + str(self._starting_parameters_type)) - elif self._fit_method == "MultiNest": + elif self._fit_method in ["MultiNest", "UltraNest"]: unsorted_keys = self._prior_limits.keys() else: raise ValueError('unexpected method error') @@ -576,6 +612,8 @@ def _check_imports(self): required_packages.add('emcee') elif self._fit_method == "MultiNest": required_packages.add('pymultinest') + elif self._fit_method == "Ultranest": + required_packages.add('cython') if self._plots is not None and 'triangle' in self._plots: required_packages.add('corner') @@ -844,7 +882,7 @@ def _check_plots_parameters_trace(self): raise ValueError( 'Unknown settings for "trace" plot: {:}'.format(unknown)) - if self._fit_method == "MultiNest": + if self._fit_method in ["MultiNest", "UltraNest"]: raise ValueError( 'Trace plot cannot be requested for MultiNest fit') @@ -897,7 +935,7 @@ def _check_other_fit_parameters(self): """ Check if there aren't any other inconsistencies between settings """ - if self._fit_method == "MultiNest": + if self._fit_method in ["MultiNest", "UltraNest"]: if self._min_values is not None or self._max_values is not None: raise ValueError("In MultiNest fitting you cannot set " "min_values or max_values") @@ -1145,7 +1183,7 @@ def _parse_fitting_parameters(self): if self._fit_method == 'EMCEE': self._parse_fitting_parameters_EMCEE() self._get_n_walkers() - elif self._fit_method == 'MultiNest': + elif self._fit_method in ['MultiNest', 'UltraNest']: self._parse_fitting_parameters_MultiNest() else: raise ValueError('internal inconsistency') @@ -1374,7 +1412,7 @@ def _set_prior_limits(self): """ if self._fit_method == 'EMCEE': self._set_prior_limits_EMCEE() - elif self._fit_method == 'MultiNest': + elif self._fit_method in ['MultiNest', 'UltraNest']: self._set_prior_limits_MultiNest() else: raise ValueError('internal bug') @@ -1459,7 +1497,7 @@ def _parse_fit_constraints(self): """ Parse the fitting constraints that are not simple limits on parameters """ - if self._fit_method == 'MultiNest': + if self._fit_method in ['MultiNest', 'UltraNest']: if self._fit_constraints is not None: raise NotImplementedError( "Currently no fit_constraints are implemented for " @@ -1485,7 +1523,8 @@ def _check_fit_constraints(self): """ Run checks on self._fit_constraints """ - if self._fit_constraints is not None and self._fit_method == 'MultiNest': + lst_check = ['MultiNest', 'UltraNest'] + if self._fit_constraints is not None and self._fit_method == lst_check: raise NotImplementedError( "Currently no fit_constraints are implemented for MultiNest " "fit. Please contact Radek Poleski with a specific request.") @@ -1947,7 +1986,7 @@ def _get_example_parameters(self): elif self._task == 'fit': if self._fit_method == 'EMCEE': parameters.update(self._get_example_parameters_EMCEE()) - elif self._fit_method == 'MultiNest': + elif self._fit_method in ['MultiNest', 'UltraNest']: means = 0.5 * (self._max_values + self._min_values) parameters.update(dict(zip(self._fit_parameters, means))) if "x_caustic_in" in self._fit_parameters: @@ -2418,6 +2457,8 @@ def _setup_fit(self): self._setup_fit_EMCEE() elif self._fit_method == 'MultiNest': self._setup_fit_MultiNest() + elif self._fit_method == 'UltraNest': + self._setup_fit_UltraNest() else: raise ValueError('internal bug') @@ -2441,6 +2482,15 @@ def _setup_fit_MultiNest(self): if self._return_fluxes: self._kwargs_MultiNest['n_params'] += self._n_fluxes + def _setup_fit_UltraNest(self): + """ + Setup UltraNest fit -- Raphael + """ + self._sampler = ultranest.ReactiveNestedSampler( + self._fit_parameters, + self._ln_like, transform=self._transform_unit_cube_un + ) + def _transform_unit_cube(self, cube, n_dims, n_params): """ Transform MulitNest unit cube to microlensing parameters. @@ -2477,7 +2527,34 @@ def _transform_unit_cube(self, cube, n_dims, n_params): cube[i] = fluxes[i-n_dims] self._last_fluxes = fluxes + def _transform_unit_cube_un(self, cube, n_dims=3, n_params=3): + """ + Transform UltraNest unit cube to microlensing parameters. + + [...] + """ + cube_out = self._min_values + cube[:n_dims] * self._range_values + + # Raphael, check: are "x_caustic_in" lines needed here? + + # if hasattr(self, '_last_theta'): + # self._prev_last_theta = self._last_theta.copy() + # else: + # self._prev_last_theta = cube_out + # self._last_ln_like = self._ln_like(cube_out) + # self._last_theta = cube_out + + # Raphael, check: are self._return_fluxes lines needed here? + if self._return_fluxes: + fluxes = self._get_fluxes() + for i in range(n_dims, n_params): + cube[i] = fluxes[i-n_dims] + self._last_fluxes = fluxes + + return cube_out + def _ln_like_MN(self, theta, n_dim, n_params, lnew): + # def _ln_like_MN(self, theta, n_dim=3, n_params=3, lnew=-1.e300): """ Calculate likelihood and save if its best model. This is used for MultiNest fitting. @@ -2512,6 +2589,8 @@ def _run_fit(self): self._run_fit_EMCEE() elif self._fit_method == 'MultiNest': self._run_fit_MultiNest() + elif self._fit_method == 'UltraNest': + self._run_fit_UltraNest() else: raise ValueError('internal bug') @@ -2527,6 +2606,12 @@ def _run_fit_MultiNest(self): """ mn_run(**self._kwargs_MultiNest) + def _run_fit_UltraNest(self): + """ + Run Ultranest fit + """ + self.un_result = self._sampler.run() + def _finish_fit(self): """ Make the things that are necessary after the fit is done. @@ -2602,6 +2687,15 @@ def _parse_results(self): self._save_posterior_EMCEE() elif self._fit_method == "MultiNest": self._parse_results_MultiNest() + elif self._fit_method == "UltraNest": + result = self.un_result['maximum_likelihood']['point'] + stdev = self.un_result['posterior']['stdev'] + print("\n--\nUltraNest results:") + for (i, param) in enumerate(self.un_result['paramnames']): + print(param, ' =', result[i], ' +/-', stdev[i]) + print('logl =', self.un_result['maximum_likelihood']['logl']) + print('logz =', self.un_result['logz'], '+/-', + self.un_result['logzerr']) else: raise ValueError('internal bug') From 294a31a10abbc4eec4cd274e689703882d1404c3 Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Wed, 21 Aug 2024 12:28:09 +0200 Subject: [PATCH 02/21] Finished parsing fitting_parameters for UltraNest --- examples/example_16/ob08092-o4_MN.yaml | 9 +- examples/example_16/ulens_model_fit.py | 110 ++++++++++++++++++++----- 2 files changed, 95 insertions(+), 24 deletions(-) diff --git a/examples/example_16/ob08092-o4_MN.yaml b/examples/example_16/ob08092-o4_MN.yaml index aef222615..45944b976 100644 --- a/examples/example_16/ob08092-o4_MN.yaml +++ b/examples/example_16/ob08092-o4_MN.yaml @@ -6,11 +6,16 @@ prior_limits: u_0: [0.3, 0.65] t_E: 16. 19.6 fitting_parameters: + # MultiNest only (basename, multimodal, evidence tolerance) basename: out_ob08092_O4_MN- multimodal: True + # evidence tolerance: 0.5 + # UltraNest only (log directory, derived parameter names, show_status) + log directory: ultranest_outputs/ + # derived parameter names: source_flux blending_flux + show_status: True + # `n_live_points` can be used in MultiNest and UltraNest n_live_points: 1000 -# Default settings of other parameters: -# evidence tolerance: 0.5 plots: best model: file: out_ob08092_O4_MN_model.png diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index 9ad28d5a6..527d290b8 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -227,6 +227,22 @@ class UlensModelFit(object): ``evidence tolerance`` (*float*) - requested tolerance of ln(Z) calculation; default is 0.5 and should work well in most cases. + Third - UltraNest. There are no required parameters, but a few + can be provided. Currently accepted ones are: + + ``log directory`` (*str*) - where to store output files. If given, + there is a check if directory exists. If not given, no outputs + are saved. + + ``derived parameter names`` (*str*) - names of additional derived + parameters created by transform... Continue here! + + ``show_status`` (*bool*) - whether to show integration progress + as a status line or not + + ``n_live_points`` (*int*) - number of live points, default value + is 400. + fit_constraints: *dict* Constraints on model other than minimal and maximal values. @@ -884,7 +900,7 @@ def _check_plots_parameters_trace(self): if self._fit_method in ["MultiNest", "UltraNest"]: raise ValueError( - 'Trace plot cannot be requested for MultiNest fit') + f'Trace plot cannot be requested for {self._fit_method}.') self._parse_plots_parameter_shift_t_0(self._plots['trace']) @@ -937,8 +953,8 @@ def _check_other_fit_parameters(self): """ if self._fit_method in ["MultiNest", "UltraNest"]: if self._min_values is not None or self._max_values is not None: - raise ValueError("In MultiNest fitting you cannot set " - "min_values or max_values") + raise ValueError("In {:} fitting you cannot set min_values " + "or max_values".format(self._fit_method)) def _parse_methods(self, methods): """ @@ -1165,11 +1181,12 @@ def _get_parameters_latex(self): if self._fit_constraints is not None: if 'posterior parsing' in self._fit_constraints: - if 'abs' in self._fit_constraints['posterior parsing']: - settings = self._fit_constraints['posterior parsing']['abs'] - if not isinstance(settings, list): - raise ValueError("Error: fit_constraints -> posterior parsing -> abs - list expected") - for key in self._fit_constraints['posterior parsing']['abs']: + settings = self._fit_constraints['posterior parsing'] + if 'abs' in settings: + if not isinstance(settings['abs'], list): + raise ValueError("Error: fit_constraints -> posterior" + " parsing -> abs - list expected") + for key in settings['abs']: conversion[key] = "|" + conversion[key] + "|" self._fit_parameters_latex = [ @@ -1183,8 +1200,10 @@ def _parse_fitting_parameters(self): if self._fit_method == 'EMCEE': self._parse_fitting_parameters_EMCEE() self._get_n_walkers() - elif self._fit_method in ['MultiNest', 'UltraNest']: + elif self._fit_method == 'MultiNest': self._parse_fitting_parameters_MultiNest() + elif self._fit_method == 'UltraNest': + self._parse_fitting_parameters_UltraNest() else: raise ValueError('internal inconsistency') @@ -1258,8 +1277,8 @@ def _check_required_and_allowed_parameters(self, required, allowed): for required_ in required: if required_ not in settings: - raise ValueError('EMCEE method requires fitting parameter: ' + - required_) + msg = '{:} method requires fitting parameter: {:}' + raise ValueError(msg.format(self._fit_method, required_)) if len(set(settings.keys()) - set(full)) > 0: raise ValueError('Unexpected fitting parameters: ' + @@ -1338,6 +1357,11 @@ def _parse_fitting_parameters_MultiNest(self): floats = ['sampling efficiency', 'evidence tolerance'] allowed = strings + bools + ints + floats + only_UltraNest = ['log directory', 'derived parameter names', + 'show status'] + for item in only_UltraNest: + settings.pop(item, None) + self._check_required_and_allowed_parameters(required, allowed) self._check_parameters_types(settings, bools, ints, floats, strings) @@ -1349,7 +1373,7 @@ def _parse_fitting_parameters_MultiNest(self): same_keys = ["multimodal", "n_live_points"] keys = {**keys, **{key: key for key in same_keys}} - self._set_dict_safetly(self._kwargs_MultiNest, settings, keys) + self._set_dict_safely(self._kwargs_MultiNest, settings, keys) self._kwargs_MultiNest['importance_nested_sampling'] = ( not self._kwargs_MultiNest['multimodal']) @@ -1361,7 +1385,7 @@ def _parse_fitting_parameters_MultiNest(self): self._MN_temporary_files = True self._check_output_files_MultiNest() - def _set_dict_safetly(self, target, source, keys_mapping): + def _set_dict_safely(self, target, source, keys_mapping): """ For each key in keys_mapping (*dict*) check if it is in source (*dict*). If it is, then set @@ -1406,12 +1430,51 @@ def _check_output_files_MultiNest(self): message += "(unless you kill this process)!!!\n" warnings.warn(message + str(existing) + "\n") + def _parse_fitting_parameters_UltraNest(self): + """ + make sure MultiNest fitting parameters are properly defined + """ + self._kwargs_UltraNest = dict() + self._kwargs_UltraNest['viz_callback'] = False + + settings = self._fitting_parameters + if settings is None: + settings = dict() + + required = [] + bools = ['show_status'] + ints = ['n_live_points'] + strings = ['log directory', 'derived parameter names'] + floats = [] + allowed = strings + bools + ints + floats + + only_MultiNest = ['basename', 'multimodal', 'evidence tolerance'] + for item in only_MultiNest: + settings.pop(item, None) + + self._check_required_and_allowed_parameters(required, allowed) + self._check_parameters_types(settings, bools, ints, floats, strings) + value = settings.pop("log directory") + if value is not None: + if path.exists(value) and path.isdir(value): + self._un_log_dir = value + else: + raise ValueError("log directory value in fitting_parameters" + "does not exist.") + value = settings.pop("derived parameter names", "") + self._un_derived_params_names = value.split() + + keys = {"n_live_points": "min_num_live_points"} + same_keys = ["show_status"] + keys = {**keys, **{key: key for key in same_keys}} + self._set_dict_safely(self._kwargs_UltraNest, settings, keys) + def _set_prior_limits(self): """ Set minimum and maximum values of the prior space """ if self._fit_method == 'EMCEE': - self._set_prior_limits_EMCEE() + self._set_prior_limits_EMCEE() # Raphael: never happens? elif self._fit_method in ['MultiNest', 'UltraNest']: self._set_prior_limits_MultiNest() else: @@ -2488,7 +2551,9 @@ def _setup_fit_UltraNest(self): """ self._sampler = ultranest.ReactiveNestedSampler( self._fit_parameters, - self._ln_like, transform=self._transform_unit_cube_un + self._ln_like, transform=self._transform_unit_cube_un, + derived_param_names=self._un_derived_params_names, + log_dir=self._un_log_dir ) def _transform_unit_cube(self, cube, n_dims, n_params): @@ -2610,7 +2675,7 @@ def _run_fit_UltraNest(self): """ Run Ultranest fit """ - self.un_result = self._sampler.run() + self._un_result = self._sampler.run(**self._kwargs_UltraNest) def _finish_fit(self): """ @@ -2688,14 +2753,15 @@ def _parse_results(self): elif self._fit_method == "MultiNest": self._parse_results_MultiNest() elif self._fit_method == "UltraNest": - result = self.un_result['maximum_likelihood']['point'] - stdev = self.un_result['posterior']['stdev'] + # self._sampler.print_results() + result = self._un_result['maximum_likelihood']['point'] + stdev = self._un_result['posterior']['stdev'] print("\n--\nUltraNest results:") - for (i, param) in enumerate(self.un_result['paramnames']): + for (i, param) in enumerate(self._un_result['paramnames']): print(param, ' =', result[i], ' +/-', stdev[i]) - print('logl =', self.un_result['maximum_likelihood']['logl']) - print('logz =', self.un_result['logz'], '+/-', - self.un_result['logzerr']) + print('logl =', self._un_result['maximum_likelihood']['logl']) + print('logz =', self._un_result['logz'], '+/-', + self._un_result['logzerr']) else: raise ValueError('internal bug') From fbb5885c4d46663f2e7b406fb4625411ded5ad32 Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Wed, 21 Aug 2024 18:30:43 +0200 Subject: [PATCH 03/21] Removed repeated check of self._fit_constraints, minor changes in description --- examples/example_16/ulens_model_fit.py | 37 +++++++++++--------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index 527d290b8..bac7b038e 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -184,7 +184,7 @@ class UlensModelFit(object): fitting_parameters: *dict* Parameters of the fit function. They depend on the method used - - we discuss EMCEE and pyMultiNest below. + we discuss EMCEE, pyMultiNest and UltraNest below. First - EMCEE. The required parameter is ``n_steps``. You can also specify ``n_burn`` and ``n_walkers``. The ``n_burn`` @@ -218,7 +218,7 @@ class UlensModelFit(object): the posterior to be detected and reported separately? ``n_live_points`` (*int*) - number of live points, default value - is 400. + is 400. Also valid for UltraNest. ``sampling efficiency`` (*float*) - requested sampling efficiency. MultiNest documentation suggests 0.8 (default value) for parameter @@ -235,13 +235,15 @@ class UlensModelFit(object): are saved. ``derived parameter names`` (*str*) - names of additional derived - parameters created by transform... Continue here! + parameters created by transform. In microlensing, they are usually + the source(s) and blending fluxes. If not given, they are ignored + in the transform function. ``show_status`` (*bool*) - whether to show integration progress - as a status line or not + as a status line or not. Default is *True*. - ``n_live_points`` (*int*) - number of live points, default value - is 400. + ``n_live_points`` (*int*) - minimum number of live points + throughout the run. Default value is 400. fit_constraints: *dict* Constraints on model other than minimal and maximal values. @@ -1474,7 +1476,7 @@ def _set_prior_limits(self): Set minimum and maximum values of the prior space """ if self._fit_method == 'EMCEE': - self._set_prior_limits_EMCEE() # Raphael: never happens? + self._set_prior_limits_EMCEE() elif self._fit_method in ['MultiNest', 'UltraNest']: self._set_prior_limits_MultiNest() else: @@ -1531,7 +1533,7 @@ def _set_prior_limits_MultiNest(self): max_values = [] for parameter in self._fit_parameters: if parameter not in self._prior_limits: - raise ValueError("interal issue") + raise ValueError("internal issue") values = self._prior_limits[parameter] if isinstance(values, str): values = values.split() @@ -1560,13 +1562,7 @@ def _parse_fit_constraints(self): """ Parse the fitting constraints that are not simple limits on parameters """ - if self._fit_method in ['MultiNest', 'UltraNest']: - if self._fit_constraints is not None: - raise NotImplementedError( - "Currently no fit_constraints are implemented for " - "MultiNest fit. Please contact Radek Poleski with " - "a specific request.") - + self._check_fit_constraints() self._prior_t_E = None self._priors = None @@ -1574,7 +1570,6 @@ def _parse_fit_constraints(self): self._set_default_fit_constraints() return - self._check_fit_constraints() self._parse_fit_constraints_keys() self._parse_fit_constraints_fluxes() self._parse_fit_constraints_posterior() @@ -1587,15 +1582,15 @@ def _check_fit_constraints(self): Run checks on self._fit_constraints """ lst_check = ['MultiNest', 'UltraNest'] - if self._fit_constraints is not None and self._fit_method == lst_check: - raise NotImplementedError( - "Currently no fit_constraints are implemented for MultiNest " - "fit. Please contact Radek Poleski with a specific request.") + if self._fit_constraints is not None and self._fit_method in lst_check: + msg = "Currently no fit_constraints are implemented for {:} " + \ + "fit. Please contact Radek Poleski with a specific request." + raise NotImplementedError(msg.format(self._fit_method)) if isinstance(self._fit_constraints, list): raise TypeError( "In version 0.5.0 we've changed type of 'fit_constraints' " + - "from list to dict. Please correct you input and re-run " + + "from list to dict. Please correct your input and re-run " + "the code. Most probably what you need is:\n" + "fit_constraints = {'no_negative_blending_flux': True}") From 9e9b14eb865d12d72cb8e9034ee49f1b90c54f7b Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Thu, 22 Aug 2024 11:05:26 +0200 Subject: [PATCH 04/21] Checked entire code for changes, only missing parse UltraNest results --- examples/example_16/ob08092-o4_MN.yaml | 2 +- examples/example_16/ulens_model_fit.py | 49 ++++++++++++++------------ 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/examples/example_16/ob08092-o4_MN.yaml b/examples/example_16/ob08092-o4_MN.yaml index 45944b976..a3be9ea35 100644 --- a/examples/example_16/ob08092-o4_MN.yaml +++ b/examples/example_16/ob08092-o4_MN.yaml @@ -12,7 +12,7 @@ fitting_parameters: # evidence tolerance: 0.5 # UltraNest only (log directory, derived parameter names, show_status) log directory: ultranest_outputs/ - # derived parameter names: source_flux blending_flux + derived parameter names: source_flux blending_flux show_status: True # `n_live_points` can be used in MultiNest and UltraNest n_live_points: 1000 diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index bac7b038e..6d6a4f5c6 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -1637,7 +1637,7 @@ def _check_color_constraints_conflict(self, allowed_keys_color): str(used_keys.intersection(allowed_keys_color)-{'color'})) def _parse_fit_constraints_fluxes(self): - """msg += + """ Process each constraint fit_constraints. """ for key, value in self._fit_constraints.items(): @@ -1723,10 +1723,8 @@ def _parse_fit_constraints_prior(self): priors = dict() for (key, value) in self._fit_constraints['prior'].items(): if key == 't_E': - if value == "Mroz et al. 2017": - self._prior_t_E = 'Mroz+17' - elif value == "Mroz et al. 2020": - self._prior_t_E = 'Mroz+20' + if value in ["Mroz et al. 2017", "Mroz et al. 2020"]: + self._prior_t_E = value.replace(" et al. 20", "+") else: raise ValueError("Unrecognized t_E prior: " + value) self._read_prior_t_E_data() @@ -1761,15 +1759,15 @@ def _parse_fit_constraints_posterior(self): return if self._fit_method != "EMCEE": - raise ValueError('Input in "posterior parsing" is allowed only for EMCEE') + raise ValueError('Input in "posterior parsing" is allowed only' + ' for EMCEE') allowed_keys = {"abs"} settings = self._fit_constraints['posterior parsing'] unknown = set(settings.keys()) - allowed_keys if len(unknown) > 0: - raise KeyError( - "Unrecognized key in fit_constraints -> 'posterior parsing': " + - str(unknown)) + msg = "Unrecognized key in fit_constraints -> 'posterior parsing':" + raise KeyError(msg + ' ' + str(unknown)) if 'abs' in settings: self._parse_posterior_abs = settings['abs'] @@ -2542,8 +2540,16 @@ def _setup_fit_MultiNest(self): def _setup_fit_UltraNest(self): """ - Setup UltraNest fit -- Raphael + Prepare UltraNest fit, declaring sampler instance """ + if self._return_fluxes and len(self._un_derived_params_names) == 0: + self._un_derived_params_names = ["source_flux", "blending_flux"] + if self._n_fluxes > 2: + self._un_derived_params_names[0] += "_1" + for i in range(2, self._n_fluxes): + name = "source_flux_{:}".format(i) + self._un_derived_params_names.insert(i-1, name) + self._sampler = ultranest.ReactiveNestedSampler( self._fit_parameters, self._ln_like, transform=self._transform_unit_cube_un, @@ -2587,34 +2593,29 @@ def _transform_unit_cube(self, cube, n_dims, n_params): cube[i] = fluxes[i-n_dims] self._last_fluxes = fluxes - def _transform_unit_cube_un(self, cube, n_dims=3, n_params=3): + def _transform_unit_cube_un(self, cube): """ Transform UltraNest unit cube to microlensing parameters. - [...] + Based on _transform_unit_cube() for MultiNest method and tutorials + from UltraNest documentation: + https://johannesbuchner.github.io/UltraNest/example-sine-line.html """ + n_dims = self._n_fit_parameters + n_params = n_dims + self._n_fluxes * self._return_fluxes cube_out = self._min_values + cube[:n_dims] * self._range_values - # Raphael, check: are "x_caustic_in" lines needed here? - - # if hasattr(self, '_last_theta'): - # self._prev_last_theta = self._last_theta.copy() - # else: - # self._prev_last_theta = cube_out - # self._last_ln_like = self._ln_like(cube_out) - # self._last_theta = cube_out + # Check with Radek: are "x_caustic_in" lines needed here? - # Raphael, check: are self._return_fluxes lines needed here? if self._return_fluxes: fluxes = self._get_fluxes() for i in range(n_dims, n_params): - cube[i] = fluxes[i-n_dims] + cube_out = np.append(cube_out, fluxes[i-n_dims]) self._last_fluxes = fluxes return cube_out def _ln_like_MN(self, theta, n_dim, n_params, lnew): - # def _ln_like_MN(self, theta, n_dim=3, n_params=3, lnew=-1.e300): """ Calculate likelihood and save if its best model. This is used for MultiNest fitting. @@ -2748,6 +2749,8 @@ def _parse_results(self): elif self._fit_method == "MultiNest": self._parse_results_MultiNest() elif self._fit_method == "UltraNest": + # Raphael: continue from here... + # self._sampler.print_results() result = self._un_result['maximum_likelihood']['point'] stdev = self._un_result['posterior']['stdev'] From 2e29836fcb04836ce5aa3dc208b9a3786d35f9f1 Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Thu, 22 Aug 2024 14:05:17 +0200 Subject: [PATCH 05/21] Replaced _un to _UltraNest, started to parse results --- examples/example_16/ulens_model_fit.py | 85 +++++++++++++------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index 6d6a4f5c6..1d925e6c0 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -563,8 +563,8 @@ def _check_fitting_method(self): "EMCEE fitting method requires starting_parameters.") elif self._fit_method in ["MultiNest", "UltraNest"]: if self._prior_limits is None: - raise ValueError( - "pyMultiNest fitting method requires prior_limits.") + msg = "{:} fitting method requires prior_limits." + raise ValueError(msg.format(self._fit_method)) else: raise ValueError("Invalid fitting method was inserted.") @@ -955,8 +955,8 @@ def _check_other_fit_parameters(self): """ if self._fit_method in ["MultiNest", "UltraNest"]: if self._min_values is not None or self._max_values is not None: - raise ValueError("In {:} fitting you cannot set min_values " - "or max_values".format(self._fit_method)) + msg = "In {:} fitting you cannot set min_values or max_values" + raise ValueError(msg.format(self._fit_method)) def _parse_methods(self, methods): """ @@ -1459,12 +1459,12 @@ def _parse_fitting_parameters_UltraNest(self): value = settings.pop("log directory") if value is not None: if path.exists(value) and path.isdir(value): - self._un_log_dir = value + self._log_dir_UltraNest = value else: raise ValueError("log directory value in fitting_parameters" "does not exist.") value = settings.pop("derived parameter names", "") - self._un_derived_params_names = value.split() + self._derived_params_UltraNest = value.split() keys = {"n_live_points": "min_num_live_points"} same_keys = ["show_status"] @@ -2542,19 +2542,19 @@ def _setup_fit_UltraNest(self): """ Prepare UltraNest fit, declaring sampler instance """ - if self._return_fluxes and len(self._un_derived_params_names) == 0: - self._un_derived_params_names = ["source_flux", "blending_flux"] + if self._return_fluxes and len(self._derived_params_UltraNest) == 0: + self._derived_params_UltraNest = ["source_flux", "blending_flux"] if self._n_fluxes > 2: - self._un_derived_params_names[0] += "_1" + self._derived_params_UltraNest[0] += "_1" for i in range(2, self._n_fluxes): name = "source_flux_{:}".format(i) - self._un_derived_params_names.insert(i-1, name) + self._derived_params_UltraNest.insert(i-1, name) self._sampler = ultranest.ReactiveNestedSampler( self._fit_parameters, - self._ln_like, transform=self._transform_unit_cube_un, - derived_param_names=self._un_derived_params_names, - log_dir=self._un_log_dir + self._ln_like, transform=self._transform_unit_cube_UltraNest, + derived_param_names=self._derived_params_UltraNest, + log_dir=self._log_dir_UltraNest ) def _transform_unit_cube(self, cube, n_dims, n_params): @@ -2593,28 +2593,6 @@ def _transform_unit_cube(self, cube, n_dims, n_params): cube[i] = fluxes[i-n_dims] self._last_fluxes = fluxes - def _transform_unit_cube_un(self, cube): - """ - Transform UltraNest unit cube to microlensing parameters. - - Based on _transform_unit_cube() for MultiNest method and tutorials - from UltraNest documentation: - https://johannesbuchner.github.io/UltraNest/example-sine-line.html - """ - n_dims = self._n_fit_parameters - n_params = n_dims + self._n_fluxes * self._return_fluxes - cube_out = self._min_values + cube[:n_dims] * self._range_values - - # Check with Radek: are "x_caustic_in" lines needed here? - - if self._return_fluxes: - fluxes = self._get_fluxes() - for i in range(n_dims, n_params): - cube_out = np.append(cube_out, fluxes[i-n_dims]) - self._last_fluxes = fluxes - - return cube_out - def _ln_like_MN(self, theta, n_dim, n_params, lnew): """ Calculate likelihood and save if its best model. @@ -2642,6 +2620,28 @@ def _ln_like_MN(self, theta, n_dim, n_params, lnew): return ln_like + def _transform_unit_cube_UltraNest(self, cube): + """ + Transform UltraNest unit cube to microlensing parameters. + + Based on _transform_unit_cube() for MultiNest method and tutorials + from UltraNest documentation: + https://johannesbuchner.github.io/UltraNest/example-sine-line.html + """ + n_dims = self._n_fit_parameters + n_params = n_dims + self._n_fluxes * self._return_fluxes + cube_out = self._min_values + cube[:n_dims] * self._range_values + + # Check with Radek: are "x_caustic_in" lines needed here? + + if self._return_fluxes: + fluxes = self._get_fluxes() + for i in range(n_dims, n_params): + cube_out = np.append(cube_out, fluxes[i-n_dims]) + self._last_fluxes = fluxes + + return cube_out + def _run_fit(self): """ Call the method that does the fit. @@ -2671,7 +2671,7 @@ def _run_fit_UltraNest(self): """ Run Ultranest fit """ - self._un_result = self._sampler.run(**self._kwargs_UltraNest) + self._result_UltraNest = self._sampler.run(**self._kwargs_UltraNest) def _finish_fit(self): """ @@ -2752,14 +2752,15 @@ def _parse_results(self): # Raphael: continue from here... # self._sampler.print_results() - result = self._un_result['maximum_likelihood']['point'] - stdev = self._un_result['posterior']['stdev'] + result = self._result_UltraNest['maximum_likelihood']['point'] + stdev = self._result_UltraNest['posterior']['stdev'] + log_like = self._result_UltraNest['maximum_likelihood']['logl'] print("\n--\nUltraNest results:") - for (i, param) in enumerate(self._un_result['paramnames']): + for (i, param) in enumerate(self._result_UltraNest['paramnames']): print(param, ' =', result[i], ' +/-', stdev[i]) - print('logl =', self._un_result['maximum_likelihood']['logl']) - print('logz =', self._un_result['logz'], '+/-', - self._un_result['logzerr']) + print('logl =', log_like) + print('logz =', self._result_UltraNest['logz'], '+/-', + self._result_UltraNest['logzerr']) else: raise ValueError('internal bug') From b590824056fb5e8560ed7da2543ebf525197a690 Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Thu, 22 Aug 2024 16:55:14 +0200 Subject: [PATCH 06/21] Finished parse_results for UltraNest, still make a separate YAML file --- examples/example_16/ob08092-o4_MN.yaml | 6 +++ examples/example_16/ulens_model_fit.py | 57 ++++++++++++++++++-------- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/examples/example_16/ob08092-o4_MN.yaml b/examples/example_16/ob08092-o4_MN.yaml index a3be9ea35..2166f3930 100644 --- a/examples/example_16/ob08092-o4_MN.yaml +++ b/examples/example_16/ob08092-o4_MN.yaml @@ -19,5 +19,11 @@ fitting_parameters: plots: best model: file: out_ob08092_O4_MN_model.png + second Y scale: + magnifications: optimal triangle: file: out_ob08092_O4_MN_triangle.png + shift t_0: False +other_output: + yaml output: + file name: ob03235_2_all_results_UN.yaml diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index 1d925e6c0..4551ec502 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -2749,18 +2749,7 @@ def _parse_results(self): elif self._fit_method == "MultiNest": self._parse_results_MultiNest() elif self._fit_method == "UltraNest": - # Raphael: continue from here... - - # self._sampler.print_results() - result = self._result_UltraNest['maximum_likelihood']['point'] - stdev = self._result_UltraNest['posterior']['stdev'] - log_like = self._result_UltraNest['maximum_likelihood']['logl'] - print("\n--\nUltraNest results:") - for (i, param) in enumerate(self._result_UltraNest['paramnames']): - print(param, ' =', result[i], ' +/-', stdev[i]) - print('logl =', log_like) - print('logz =', self._result_UltraNest['logz'], '+/-', - self._result_UltraNest['logzerr']) + self._parse_results_UltraNest() else: raise ValueError('internal bug') @@ -2857,7 +2846,7 @@ def _print_results(self, data, names="parameters", mode=None): if self._fit_method == "EMCEE": results = self._get_weighted_percentile(data) - elif self._fit_method == "MultiNest": + elif self._fit_method in ["MultiNest", "UltraNest"]: if mode is None: weights = self._samples_flat_weights else: @@ -2906,7 +2895,7 @@ def _print_yaml_results(self, data, names="parameters", mode=None): if self._fit_method == "EMCEE": results = self._get_weighted_percentile(data) - elif self._fit_method == "MultiNest": + elif self._fit_method in ["MultiNest", "UltraNest"]: if mode is None: weights = self._samples_flat_weights else: @@ -3220,12 +3209,44 @@ def _extract_posterior_samples_MultiNest(self): def _get_fluxes_to_print_MultiNest(self): """ - prepare values to be printed for EMCEE fitting + prepare flux values to be printed for MultiNest and Ultranest fitting """ - index = 2 + self._n_fit_parameters - data = self._analyzer_data[:, index:] + if self._fit_method == "MultiNest": + index = 2 + self._n_fit_parameters + data = self._analyzer_data + elif self._fit_method == "UltraNest": + index = self._n_fit_parameters + data = self._result_UltraNest['weighted_samples']['points'] + + return data[:, index:] - return data + def _parse_results_UltraNest(self): + """ + Parse results of UltraNest fitting. + Functions that print and save EMCEE results are also called here. + """ + # re-weighted posterior samples + # self._samples_flat = self._result_UltraNest['samples'][:, :-2] + # weighted samples from the posterior + weighted_samples = self._result_UltraNest['weighted_samples'] + self._samples_flat = weighted_samples['points'][:, :-2] + self._samples_flat_weights = weighted_samples['weights'] + self._sampler.print_results() + + max_like = self._result_UltraNest['maximum_likelihood'] + self._best_model_ln_prob = max_like['logl'] + self._best_model_theta = max_like['point'][:self._n_fit_parameters] + self._best_model_fluxes = max_like['point'][self._n_fit_parameters:] + self._parse_results_MultiNest_singlemode() + + self._shift_t_0_in_samples() + self._print_best_model() + if self._yaml_results: + self._print_yaml_best_model() + ln_ev = self._result_UltraNest['logz_single'] + ln_ev_err = self._result_UltraNest['logzerr_single'] + lns = " ln_ev: [{:.5f}, +{:.5f}, -{:.5f}]" + print(lns.format(ln_ev, ln_ev_err, ln_ev_err), **self._yaml_kwargs) def _write_residuals(self): """ From 5e31f20f79cd9b7dae94348c7e1164a83d701256 Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Fri, 23 Aug 2024 00:09:15 +0200 Subject: [PATCH 07/21] Added separate YAML file for UltraNest --- examples/example_16/ob03235_2_full.yaml | 2 ++ examples/example_16/ob08092-o4_MN.yaml | 14 +++----------- examples/example_16/ob08092-o4_UN.yaml | 23 +++++++++++++++++++++++ examples/example_16/ulens_model_fit.py | 2 +- 4 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 examples/example_16/ob08092-o4_UN.yaml diff --git a/examples/example_16/ob03235_2_full.yaml b/examples/example_16/ob03235_2_full.yaml index 3be65d642..130d321e2 100644 --- a/examples/example_16/ob03235_2_full.yaml +++ b/examples/example_16/ob03235_2_full.yaml @@ -5,6 +5,8 @@ photometry_files: # - {file_name: some_K2_data.txt, phot_fmt: flux, bandpass: 'Kp', ephemerides_file: K2_ephemeris_01.dat} # For files with time starting with 245...: # - {..., add_2450000: False} +fit_method: EMCEE +# Method options: EMCEE (default), MultiNest, UltraNest model: methods: 2452800. point_source 2452833. VBBL 2452845. point_source 2452860. default method: point_source_point_lens diff --git a/examples/example_16/ob08092-o4_MN.yaml b/examples/example_16/ob08092-o4_MN.yaml index 2166f3930..103d526c9 100644 --- a/examples/example_16/ob08092-o4_MN.yaml +++ b/examples/example_16/ob08092-o4_MN.yaml @@ -1,29 +1,21 @@ photometry_files: data/OB08092/phot_ob08092_O4.dat -fit_method: UltraNest # options: EMCEE, MultiNest, UltraNest (... dynesty) +fit_method: MultiNest prior_limits: t_0: [2455379.4, 2455379.76] u_0: [0.3, 0.65] t_E: 16. 19.6 fitting_parameters: - # MultiNest only (basename, multimodal, evidence tolerance) basename: out_ob08092_O4_MN- multimodal: True + # Default settings of other parameters: # evidence tolerance: 0.5 - # UltraNest only (log directory, derived parameter names, show_status) - log directory: ultranest_outputs/ - derived parameter names: source_flux blending_flux - show_status: True - # `n_live_points` can be used in MultiNest and UltraNest n_live_points: 1000 plots: best model: file: out_ob08092_O4_MN_model.png - second Y scale: - magnifications: optimal triangle: file: out_ob08092_O4_MN_triangle.png - shift t_0: False other_output: yaml output: - file name: ob03235_2_all_results_UN.yaml + file name: ob08092_O4_MN_all_results.yaml diff --git a/examples/example_16/ob08092-o4_UN.yaml b/examples/example_16/ob08092-o4_UN.yaml new file mode 100644 index 000000000..b555d5b93 --- /dev/null +++ b/examples/example_16/ob08092-o4_UN.yaml @@ -0,0 +1,23 @@ +photometry_files: + data/OB08092/phot_ob08092_O4.dat +fit_method: UltraNest +prior_limits: + t_0: [2455379.4, 2455379.76] + u_0: [0.3, 0.65] + t_E: 16. 19.6 +fitting_parameters: + log directory: ultranest_outputs/ + derived parameter names: source_flux blending_flux + show_status: True + n_live_points: 1000 +plots: + best model: + file: out_ob08092_O4_UN_model.png + second Y scale: + magnifications: optimal + triangle: + file: out_ob08092_O4_UN_triangle.png + shift t_0: False +other_output: + yaml output: + file name: ob08092_O4_UN_all_results.yaml diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index 4551ec502..387583fd1 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -2629,7 +2629,7 @@ def _transform_unit_cube_UltraNest(self, cube): https://johannesbuchner.github.io/UltraNest/example-sine-line.html """ n_dims = self._n_fit_parameters - n_params = n_dims + self._n_fluxes * self._return_fluxes + n_params = n_dims + self._n_fluxes_per_dataset * self._return_fluxes cube_out = self._min_values + cube[:n_dims] * self._range_values # Check with Radek: are "x_caustic_in" lines needed here? From d63d6446cc59136333a2d549414be2ae6549889a Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Fri, 23 Aug 2024 13:23:05 +0200 Subject: [PATCH 08/21] Minor corrections to UltraNest parsing --- examples/example_16/ob08092-o4_minimal_MN.yaml | 3 ++- examples/example_16/ulens_model_fit.py | 17 +++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/examples/example_16/ob08092-o4_minimal_MN.yaml b/examples/example_16/ob08092-o4_minimal_MN.yaml index d83475640..7e95e4888 100644 --- a/examples/example_16/ob08092-o4_minimal_MN.yaml +++ b/examples/example_16/ob08092-o4_minimal_MN.yaml @@ -1,6 +1,7 @@ photometry_files: data/OB08092/phot_ob08092_O4.dat -fit_method: UltraNest # options: EMCEE, MultiNest, UltraNest (... dynesty) +fit_method: MultiNest +# fit_method options if prior_limits are given: MultiNest and UltraNest prior_limits: t_0: [2455379.4, 2455379.76] u_0: [0.46, 0.65] diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index 387583fd1..504b9879f 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -1456,13 +1456,14 @@ def _parse_fitting_parameters_UltraNest(self): self._check_required_and_allowed_parameters(required, allowed) self._check_parameters_types(settings, bools, ints, floats, strings) - value = settings.pop("log directory") - if value is not None: - if path.exists(value) and path.isdir(value): - self._log_dir_UltraNest = value - else: + self._log_dir_UltraNest = settings.pop("log directory", None) + if self._log_dir_UltraNest is not None: + if not path.exists(self._log_dir_UltraNest): raise ValueError("log directory value in fitting_parameters" "does not exist.") + elif not path.isdir(self._log_dir_UltraNest): + raise ValueError("log directory value in fitting_parameters" + "exists, but it is a file.") value = settings.pop("derived parameter names", "") self._derived_params_UltraNest = value.split() @@ -2671,6 +2672,9 @@ def _run_fit_UltraNest(self): """ Run Ultranest fit """ + self._kwargs_UltraNest['dlogz'] = 100. # 0.5... 100. took 15min + self._kwargs_UltraNest['dKL'] = 100. # 0.5 + self._kwargs_UltraNest['frac_remain'] = 0.005 self._result_UltraNest = self._sampler.run(**self._kwargs_UltraNest) def _finish_fit(self): @@ -3229,7 +3233,8 @@ def _parse_results_UltraNest(self): # self._samples_flat = self._result_UltraNest['samples'][:, :-2] # weighted samples from the posterior weighted_samples = self._result_UltraNest['weighted_samples'] - self._samples_flat = weighted_samples['points'][:, :-2] + index = self._n_fit_parameters + self._samples_flat = weighted_samples['points'][:, :index] self._samples_flat_weights = weighted_samples['weights'] self._sampler.print_results() From f3510969d5ba46862a40a124da28fe45c9acaa80 Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Mon, 26 Aug 2024 19:30:28 +0200 Subject: [PATCH 09/21] Last changes before PR, added parameters to UltraNest, yaml files --- examples/example_16/ob03235_2_full.yaml | 22 ++++++++++++++- examples/example_16/ob08092-o4_UN.yaml | 8 ++++++ examples/example_16/ulens_model_fit.py | 36 ++++++++++++++++++------- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/examples/example_16/ob03235_2_full.yaml b/examples/example_16/ob03235_2_full.yaml index 130d321e2..c0a61d25a 100644 --- a/examples/example_16/ob03235_2_full.yaml +++ b/examples/example_16/ob03235_2_full.yaml @@ -6,7 +6,12 @@ photometry_files: # For files with time starting with 245...: # - {..., add_2450000: False} fit_method: EMCEE -# Method options: EMCEE (default), MultiNest, UltraNest +# Options: EMCEE, MultiNest, UltraNest. +# If not given, EMCEE is used if starting_parameters are given or MultiNest +# if prior_limits are given. +# If MultiNest/UltraNest is selected: starting_parameters, fit_constraints, +# min_values and max_value cannot be provided. The trace plot cannot be +# generated as well. model: methods: 2452800. point_source 2452833. VBBL 2452845. point_source 2452860. default method: point_source_point_lens @@ -78,12 +83,27 @@ max_values: s: 2. alpha: 360. fitting_parameters: + ## EMCEE only n_walkers: 20 n_steps: 4 n_burn: 2 progress: True posterior file: ob03235_2_models.npy posterior file fluxes: all + ## MultiNest only (basename, multimodal, evidence tolerance) + # basename: out_ob08092_O4_MN- + # multimodal: True + # # evidence tolerance: 0.5 + ## UltraNest only (log directory, derived parameter names, show_status, + ## dlogz, frac_remain, max_num_improvement_loops) + # log directory: ultranest_outputs/ + # derived parameter names: source_flux blending_flux + # show_status: True + # dlogz: 2. + # frac_remain: 0.5 + # max_num_improvement_loops: 0 + ## Both for MultiNest and UltraNest (number of live points) + # n_live_points: 20 plots: best model: # You can skip the line below - the light curve will be plotted on screen. diff --git a/examples/example_16/ob08092-o4_UN.yaml b/examples/example_16/ob08092-o4_UN.yaml index b555d5b93..2cd8b6a05 100644 --- a/examples/example_16/ob08092-o4_UN.yaml +++ b/examples/example_16/ob08092-o4_UN.yaml @@ -10,6 +10,14 @@ fitting_parameters: derived parameter names: source_flux blending_flux show_status: True n_live_points: 1000 + # `n_live_points` can also be named `min_num_live_points` + # If it is smaller than 40, `cluster_num_live_points` is also reduced. + # UltraNest may increase n_live_points if it is too low to achieve the + # logz accuracy (default=0.5). It can be avoided increasing dlogz: + dlogz: 2. + # The parameters below can reduce runtime (default is -1 and 0.01) + # frac_remain: 0.5 + # max_num_improvement_loops: 0 plots: best model: file: out_ob08092_O4_UN_model.png diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index 504b9879f..100830546 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -242,9 +242,22 @@ class UlensModelFit(object): ``show_status`` (*bool*) - whether to show integration progress as a status line or not. Default is *True*. - ``n_live_points`` (*int*) - minimum number of live points + ``min_num_live_points`` (*int*) - minimum number of live points throughout the run. Default value is 400. + ``dlogz`` (*float*) - Target evidence uncertainty, in order to + obtain a logz error below a threshold. Default value is 0.5. + It can be increased to allow `min_num_live_points` values below: + sqrt(iterations) / dlogz = sqrt(1000) / 0.5 ~ 64. + + ``frac_remain`` (*float*) - Integrate until this fraction of the + integral is left in the remainder. Numbers smaller than 0.01 + ensure that peaks are discovered, higher numbers can be set if + the posterior is simple. Default value is 0.01. + + ``max_num_improvement_loops`` (*int*) - Limit the number of times + the algorithm is repeated to improve. Default value is -1. + fit_constraints: *dict* Constraints on model other than minimal and maximal values. @@ -1360,7 +1373,8 @@ def _parse_fitting_parameters_MultiNest(self): allowed = strings + bools + ints + floats only_UltraNest = ['log directory', 'derived parameter names', - 'show status'] + 'show status', 'dlogz', 'frac_remain', + 'max_num_improvement_loops'] for item in only_UltraNest: settings.pop(item, None) @@ -1434,7 +1448,7 @@ def _check_output_files_MultiNest(self): def _parse_fitting_parameters_UltraNest(self): """ - make sure MultiNest fitting parameters are properly defined + Make sure UltraNest fitting parameters are properly defined """ self._kwargs_UltraNest = dict() self._kwargs_UltraNest['viz_callback'] = False @@ -1445,9 +1459,11 @@ def _parse_fitting_parameters_UltraNest(self): required = [] bools = ['show_status'] - ints = ['n_live_points'] + ints = ['min_num_live_points', 'max_num_improvement_loops'] + if 'n_live_points' in settings: + ints[0] = 'n_live_points' strings = ['log directory', 'derived parameter names'] - floats = [] + floats = ['dlogz', 'frac_remain'] allowed = strings + bools + ints + floats only_MultiNest = ['basename', 'multimodal', 'evidence tolerance'] @@ -1468,7 +1484,8 @@ def _parse_fitting_parameters_UltraNest(self): self._derived_params_UltraNest = value.split() keys = {"n_live_points": "min_num_live_points"} - same_keys = ["show_status"] + same_keys = ["min_num_live_points", 'max_num_improvement_loops', + "show_status", "dlogz", "frac_remain"] keys = {**keys, **{key: key for key in same_keys}} self._set_dict_safely(self._kwargs_UltraNest, settings, keys) @@ -2672,9 +2689,10 @@ def _run_fit_UltraNest(self): """ Run Ultranest fit """ - self._kwargs_UltraNest['dlogz'] = 100. # 0.5... 100. took 15min - self._kwargs_UltraNest['dKL'] = 100. # 0.5 - self._kwargs_UltraNest['frac_remain'] = 0.005 + min_n_live = self._kwargs_UltraNest['min_num_live_points'] + cluster_n_live = 40 if min_n_live >= 40 else min_n_live + self._kwargs_UltraNest['cluster_num_live_points'] = cluster_n_live + self._result_UltraNest = self._sampler.run(**self._kwargs_UltraNest) def _finish_fit(self): From a05311eca09f8b659534d4257076ebc3f6d45c2d Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Mon, 26 Aug 2024 22:30:00 +0200 Subject: [PATCH 10/21] Minor changes before pull request --- examples/example_16/ulens_model_fit.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index 100830546..1a850bb03 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -2558,7 +2558,10 @@ def _setup_fit_MultiNest(self): def _setup_fit_UltraNest(self): """ - Prepare UltraNest fit, declaring sampler instance + Prepare UltraNest fit, declaring sampler instance. + If the names of the derived parameters are not given, the source + and blending fluxes are assigned, with the former containing _1 + and _2 in case of multiple sources. """ if self._return_fluxes and len(self._derived_params_UltraNest) == 0: self._derived_params_UltraNest = ["source_flux", "blending_flux"] @@ -2650,7 +2653,7 @@ def _transform_unit_cube_UltraNest(self, cube): n_params = n_dims + self._n_fluxes_per_dataset * self._return_fluxes cube_out = self._min_values + cube[:n_dims] * self._range_values - # Check with Radek: are "x_caustic_in" lines needed here? + # Check: are "x_caustic_in" lines needed as in line 2597? if self._return_fluxes: fluxes = self._get_fluxes() @@ -3247,9 +3250,9 @@ def _parse_results_UltraNest(self): Parse results of UltraNest fitting. Functions that print and save EMCEE results are also called here. """ - # re-weighted posterior samples + # re-weighted posterior samples: # self._samples_flat = self._result_UltraNest['samples'][:, :-2] - # weighted samples from the posterior + # weighted samples from the posterior: weighted_samples = self._result_UltraNest['weighted_samples'] index = self._n_fit_parameters self._samples_flat = weighted_samples['points'][:, :index] From cd3d78b25e44e0f384c98b0753afb66aaee501e7 Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Tue, 27 Aug 2024 12:12:27 +0200 Subject: [PATCH 11/21] Minor corrections to comments in yaml files --- examples/example_16/ob03235_2_full.yaml | 6 +++--- examples/example_16/ob08092-o4_UN.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/example_16/ob03235_2_full.yaml b/examples/example_16/ob03235_2_full.yaml index c0a61d25a..c812a993d 100644 --- a/examples/example_16/ob03235_2_full.yaml +++ b/examples/example_16/ob03235_2_full.yaml @@ -90,12 +90,12 @@ fitting_parameters: progress: True posterior file: ob03235_2_models.npy posterior file fluxes: all - ## MultiNest only (basename, multimodal, evidence tolerance) + ## MultiNest only (basename, multimodal, evidence tolerance=0.5) # basename: out_ob08092_O4_MN- # multimodal: True - # # evidence tolerance: 0.5 + # evidence tolerance: 0.5 ## UltraNest only (log directory, derived parameter names, show_status, - ## dlogz, frac_remain, max_num_improvement_loops) + ## dlogz=0.5, frac_remain=0.01, max_num_improvement_loops=-1) # log directory: ultranest_outputs/ # derived parameter names: source_flux blending_flux # show_status: True diff --git a/examples/example_16/ob08092-o4_UN.yaml b/examples/example_16/ob08092-o4_UN.yaml index 2cd8b6a05..5e63d7e63 100644 --- a/examples/example_16/ob08092-o4_UN.yaml +++ b/examples/example_16/ob08092-o4_UN.yaml @@ -15,7 +15,7 @@ fitting_parameters: # UltraNest may increase n_live_points if it is too low to achieve the # logz accuracy (default=0.5). It can be avoided increasing dlogz: dlogz: 2. - # The parameters below can reduce runtime (default is -1 and 0.01) + # The parameters below can reduce runtime (default is 0.01 and -1) # frac_remain: 0.5 # max_num_improvement_loops: 0 plots: From 74d944baaa3437c2931870a740e47ee1445cea0c Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Mon, 9 Sep 2024 17:55:05 +0200 Subject: [PATCH 12/21] ex16 - UltraNest, adding Radek comments 1 to 5 --- examples/example_16/ob03235_2_full.yaml | 4 +- examples/example_16/ulens_model_fit.py | 53 ++++++++++++++++++------- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/examples/example_16/ob03235_2_full.yaml b/examples/example_16/ob03235_2_full.yaml index c812a993d..3ecf7fd93 100644 --- a/examples/example_16/ob03235_2_full.yaml +++ b/examples/example_16/ob03235_2_full.yaml @@ -7,8 +7,8 @@ photometry_files: # - {..., add_2450000: False} fit_method: EMCEE # Options: EMCEE, MultiNest, UltraNest. -# If not given, EMCEE is used if starting_parameters are given or MultiNest -# if prior_limits are given. +# If not given, EMCEE is used if starting_parameters are given; if prior_limits +# are given, MultiNest or UltraNest is used depending on fitting_parameters. # If MultiNest/UltraNest is selected: starting_parameters, fit_constraints, # min_values and max_value cannot be provided. The trace plot cannot be # generated as well. diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index 1a850bb03..c2abe62cd 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -77,8 +77,9 @@ class UlensModelFit(object): Method of fitting. Currently accepted values are ``EMCEE``, ``MultiNest``, and ``UltraNest``. If not provided, the script will guess it based on other parameters: ``EMCEE`` is selected - if ``starting_parameters`` are provided and ``MultiNest`` is - selected if ``prior_limits`` are provided. + if ``starting_parameters`` are provided. If ``prior_limits`` are + provided, either ``MultiNest`` or ``UltraNest`` will be selected + depending on the ``fitting_parameters``. Webpage of each method: - EMCEE: https://emcee.readthedocs.io/en/stable/ @@ -124,8 +125,7 @@ class UlensModelFit(object): prior_limits: *dict* Upper and lower limits of parameters. - It only applies to pyMultiNest and UltraNest fitting. However, - if fit_method is not given, pyMultiNest will be applied. + It only applies to pyMultiNest and UltraNest fitting. Keys are MulensModel parameters and values are lists of two floats each (alternatively a string giving 2 floats can be provided - see @@ -381,15 +381,15 @@ class UlensModelFit(object): """ def __init__( - self, photometry_files, fit_method=None, + self, photometry_files, starting_parameters=None, prior_limits=None, model=None, fixed_parameters=None, min_values=None, max_values=None, fitting_parameters=None, - fit_constraints=None, plots=None, other_output=None + fit_constraints=None, plots=None, other_output=None, + fit_method=None ): self._check_MM_version() self._photometry_files = photometry_files - self._fit_method = fit_method self._starting_parameters_input = starting_parameters self._prior_limits = prior_limits self._model_parameters = model @@ -400,6 +400,7 @@ def __init__( self._fit_constraints = fit_constraints self._plots = plots self._other_output = other_output + self._fit_method = fit_method self._which_task() self._set_default_parameters() @@ -407,6 +408,7 @@ def __init__( if self._fit_method is None: self._guess_fitting_method() else: + self._fit_method = self._fit_method.lower() self._check_fitting_method() self._check_starting_parameters_type() self._set_fit_parameters_unsorted() @@ -558,7 +560,7 @@ def _guess_fitting_method(self): "which makes impossible to choose the fitting method. " "These settings indicate EMCEE and pyMultiNest " "respectively, and cannot be both set.") - method = "MultiNest" # UltraNest is also supported + method = self._guess_MultiNest_or_UltraNest() if method is None: raise ValueError( "No fitting method chosen. You can chose either 'EMCEE' or " @@ -566,15 +568,38 @@ def _guess_fitting_method(self): "starting_parameters or prior_limits, respectively.") self._fit_method = method + def _guess_MultiNest_or_UltraNest(self): + """ + Guess fit_method between MultiNest or UltraNest, based on the + provided fitting_parameters. + """ + args_MultiNest = ['basename', 'multimodal', 'evidence tolerance', + 'n_live_points'] + if all([key in args_MultiNest for key in self._fitting_parameters]): + return "MultiNest" + + args_UltraNest = ['log directory', 'derived parameter names', + 'show_status', 'dlogz', 'frac_remain', + 'max_num_improvement_loops', 'n_live_points'] + if all([key in args_UltraNest for key in self._fitting_parameters]): + return "UltraNest" + + raise ValueError( + "Cannot guess fitting method. Provide more parameters in " + "fitting_parameters.") + def _check_fitting_method(self): """ Check if fitting method is consistent with the settings. """ - if self._fit_method == "EMCEE": + if self._fit_method == "emcee": + self._fit_method = "EMCEE" if self._starting_parameters_input is None: raise ValueError( "EMCEE fitting method requires starting_parameters.") - elif self._fit_method in ["MultiNest", "UltraNest"]: + elif self._fit_method in ["multinest", "ultranest"]: + self._fit_method = self._fit_method.capitalize() + self._fit_method = self._fit_method.replace("nest", "Nest") if self._prior_limits is None: msg = "{:} fitting method requires prior_limits." raise ValueError(msg.format(self._fit_method)) @@ -643,8 +668,8 @@ def _check_imports(self): required_packages.add('emcee') elif self._fit_method == "MultiNest": required_packages.add('pymultinest') - elif self._fit_method == "Ultranest": - required_packages.add('cython') + elif self._fit_method == "UltraNest": + required_packages.add('ultranest') if self._plots is not None and 'triangle' in self._plots: required_packages.add('corner') @@ -1373,7 +1398,7 @@ def _parse_fitting_parameters_MultiNest(self): allowed = strings + bools + ints + floats only_UltraNest = ['log directory', 'derived parameter names', - 'show status', 'dlogz', 'frac_remain', + 'show_status', 'dlogz', 'frac_remain', 'max_num_improvement_loops'] for item in only_UltraNest: settings.pop(item, None) @@ -2692,7 +2717,7 @@ def _run_fit_UltraNest(self): """ Run Ultranest fit """ - min_n_live = self._kwargs_UltraNest['min_num_live_points'] + min_n_live = self._kwargs_UltraNest.get("min_num_live_points", 400) cluster_n_live = 40 if min_n_live >= 40 else min_n_live self._kwargs_UltraNest['cluster_num_live_points'] = cluster_n_live From 38372d4fdbe5ebe9c20f6d446b399357cd702771 Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Mon, 9 Sep 2024 19:30:23 +0200 Subject: [PATCH 13/21] ex16 - UltraNest, added Radek comments, except 8 --- examples/example_16/ob03235_2_full.yaml | 15 +++++++-- examples/example_16/ob08092-o4_UN.yaml | 2 +- examples/example_16/ulens_model_fit.py | 42 +++++++++---------------- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/examples/example_16/ob03235_2_full.yaml b/examples/example_16/ob03235_2_full.yaml index 3ecf7fd93..5bfb44e93 100644 --- a/examples/example_16/ob03235_2_full.yaml +++ b/examples/example_16/ob03235_2_full.yaml @@ -43,6 +43,17 @@ starting_parameters: # file: ob08092-o4_starting_file_input.txt # parameters: t_0 u_0 t_E # See also ob08092-o4_starting_file.yaml +# prior_limits: +# t_0: [2452847.5, 2452848.5] +# u_0: [0.08, 0.18] +# t_E: 58.7 64.7 +# rho: [0.0004, 0.0018] +# pi_E_N: [-0.05, 0.05] +# pi_E_E: [-0.05, 0.05] +# s: [1.077, 1.125] +# q: [0.001, 0.02] +# alpha: [219.4, 229.4] +# # Only MultiNest and UltraNest, cannot be given with starting_parameters fit_constraints: negative_blending_flux_sigma_mag: 20. #one can specify for which dataset soft constrain on blending flux should be applied: sigma dataset_label(s) @@ -94,10 +105,10 @@ fitting_parameters: # basename: out_ob08092_O4_MN- # multimodal: True # evidence tolerance: 0.5 - ## UltraNest only (log directory, derived parameter names, show_status, + ## UltraNest only (log directory, derived param names, show_status, ## dlogz=0.5, frac_remain=0.01, max_num_improvement_loops=-1) # log directory: ultranest_outputs/ - # derived parameter names: source_flux blending_flux + # derived param names: flux_s_1 flux_b_1 # show_status: True # dlogz: 2. # frac_remain: 0.5 diff --git a/examples/example_16/ob08092-o4_UN.yaml b/examples/example_16/ob08092-o4_UN.yaml index 5e63d7e63..c699545ce 100644 --- a/examples/example_16/ob08092-o4_UN.yaml +++ b/examples/example_16/ob08092-o4_UN.yaml @@ -7,7 +7,7 @@ prior_limits: t_E: 16. 19.6 fitting_parameters: log directory: ultranest_outputs/ - derived parameter names: source_flux blending_flux + derived param names: flux_s_1 flux_b_1 show_status: True n_live_points: 1000 # `n_live_points` can also be named `min_num_live_points` diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index c2abe62cd..b7830323e 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -234,7 +234,7 @@ class UlensModelFit(object): there is a check if directory exists. If not given, no outputs are saved. - ``derived parameter names`` (*str*) - names of additional derived + ``derived param names`` (*str*) - names of additional derived parameters created by transform. In microlensing, they are usually the source(s) and blending fluxes. If not given, they are ignored in the transform function. @@ -578,7 +578,7 @@ def _guess_MultiNest_or_UltraNest(self): if all([key in args_MultiNest for key in self._fitting_parameters]): return "MultiNest" - args_UltraNest = ['log directory', 'derived parameter names', + args_UltraNest = ['log directory', 'derived param names', 'show_status', 'dlogz', 'frac_remain', 'max_num_improvement_loops', 'n_live_points'] if all([key in args_UltraNest for key in self._fitting_parameters]): @@ -1397,12 +1397,6 @@ def _parse_fitting_parameters_MultiNest(self): floats = ['sampling efficiency', 'evidence tolerance'] allowed = strings + bools + ints + floats - only_UltraNest = ['log directory', 'derived parameter names', - 'show_status', 'dlogz', 'frac_remain', - 'max_num_improvement_loops'] - for item in only_UltraNest: - settings.pop(item, None) - self._check_required_and_allowed_parameters(required, allowed) self._check_parameters_types(settings, bools, ints, floats, strings) @@ -1478,7 +1472,7 @@ def _parse_fitting_parameters_UltraNest(self): self._kwargs_UltraNest = dict() self._kwargs_UltraNest['viz_callback'] = False - settings = self._fitting_parameters + settings = self._fitting_parameters.copy() if settings is None: settings = dict() @@ -1487,14 +1481,10 @@ def _parse_fitting_parameters_UltraNest(self): ints = ['min_num_live_points', 'max_num_improvement_loops'] if 'n_live_points' in settings: ints[0] = 'n_live_points' - strings = ['log directory', 'derived parameter names'] + strings = ['log directory', 'derived param names'] floats = ['dlogz', 'frac_remain'] allowed = strings + bools + ints + floats - only_MultiNest = ['basename', 'multimodal', 'evidence tolerance'] - for item in only_MultiNest: - settings.pop(item, None) - self._check_required_and_allowed_parameters(required, allowed) self._check_parameters_types(settings, bools, ints, floats, strings) self._log_dir_UltraNest = settings.pop("log directory", None) @@ -1505,8 +1495,8 @@ def _parse_fitting_parameters_UltraNest(self): elif not path.isdir(self._log_dir_UltraNest): raise ValueError("log directory value in fitting_parameters" "exists, but it is a file.") - value = settings.pop("derived parameter names", "") - self._derived_params_UltraNest = value.split() + value = settings.pop("derived param names", "") + self._derived_param_names_UltraNest = value.split() keys = {"n_live_points": "min_num_live_points"} same_keys = ["min_num_live_points", 'max_num_improvement_loops', @@ -2585,21 +2575,19 @@ def _setup_fit_UltraNest(self): """ Prepare UltraNest fit, declaring sampler instance. If the names of the derived parameters are not given, the source - and blending fluxes are assigned, with the former containing _1 - and _2 in case of multiple sources. + and blending fluxes are assigned, with indexes depending on the + number of sources (s1, s2) and datasets (_1, _2). """ - if self._return_fluxes and len(self._derived_params_UltraNest) == 0: - self._derived_params_UltraNest = ["source_flux", "blending_flux"] - if self._n_fluxes > 2: - self._derived_params_UltraNest[0] += "_1" - for i in range(2, self._n_fluxes): - name = "source_flux_{:}".format(i) - self._derived_params_UltraNest.insert(i-1, name) + if self._return_fluxes: + if len(self._derived_param_names_UltraNest) == 0: + if self._flux_names is None: + self._flux_names = self._get_fluxes_names_to_print() + self._derived_param_names_UltraNest = self._flux_names self._sampler = ultranest.ReactiveNestedSampler( self._fit_parameters, self._ln_like, transform=self._transform_unit_cube_UltraNest, - derived_param_names=self._derived_params_UltraNest, + derived_param_names=self._derived_param_names_UltraNest, log_dir=self._log_dir_UltraNest ) @@ -2678,7 +2666,7 @@ def _transform_unit_cube_UltraNest(self, cube): n_params = n_dims + self._n_fluxes_per_dataset * self._return_fluxes cube_out = self._min_values + cube[:n_dims] * self._range_values - # Check: are "x_caustic_in" lines needed as in line 2597? + # TBD: add "x_caustic_in" checks as in line 2610... if self._return_fluxes: fluxes = self._get_fluxes() From 315b0d9f9f83e518575d46dd69491e5ee2594928 Mon Sep 17 00:00:00 2001 From: radek_poleski Date: Sun, 15 Sep 2024 16:31:42 +0200 Subject: [PATCH 14/21] ex16: param -> parameters --- examples/example_16/ulens_model_fit.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index b7830323e..829941a63 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -234,7 +234,7 @@ class UlensModelFit(object): there is a check if directory exists. If not given, no outputs are saved. - ``derived param names`` (*str*) - names of additional derived + ``derived parameters names`` (*str*) - names of additional derived parameters created by transform. In microlensing, they are usually the source(s) and blending fluxes. If not given, they are ignored in the transform function. @@ -578,7 +578,7 @@ def _guess_MultiNest_or_UltraNest(self): if all([key in args_MultiNest for key in self._fitting_parameters]): return "MultiNest" - args_UltraNest = ['log directory', 'derived param names', + args_UltraNest = ['log directory', 'derived parameters names', 'show_status', 'dlogz', 'frac_remain', 'max_num_improvement_loops', 'n_live_points'] if all([key in args_UltraNest for key in self._fitting_parameters]): @@ -1481,7 +1481,7 @@ def _parse_fitting_parameters_UltraNest(self): ints = ['min_num_live_points', 'max_num_improvement_loops'] if 'n_live_points' in settings: ints[0] = 'n_live_points' - strings = ['log directory', 'derived param names'] + strings = ['log directory', 'derived parameters names'] floats = ['dlogz', 'frac_remain'] allowed = strings + bools + ints + floats @@ -1495,7 +1495,7 @@ def _parse_fitting_parameters_UltraNest(self): elif not path.isdir(self._log_dir_UltraNest): raise ValueError("log directory value in fitting_parameters" "exists, but it is a file.") - value = settings.pop("derived param names", "") + value = settings.pop("derived parameters names", "") self._derived_param_names_UltraNest = value.split() keys = {"n_live_points": "min_num_live_points"} From b489b2b341349e713d963bb469179b541b1b4a80 Mon Sep 17 00:00:00 2001 From: radek_poleski Date: Sun, 15 Sep 2024 16:33:26 +0200 Subject: [PATCH 15/21] ex16 - version update --- examples/example_16/ulens_model_fit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index 829941a63..c93b2c230 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -43,7 +43,7 @@ except Exception: raise ImportError('\nYou have to install MulensModel first!\n') -__version__ = '0.38.0' +__version__ = '0.39.0' class UlensModelFit(object): From 3f5b367060633dc027fa85fb009e8f86f6625875 Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Mon, 16 Sep 2024 17:28:22 +0200 Subject: [PATCH 16/21] Added UltraNest to make transform in same function as MultiNest --- examples/example_16/ob03235_2_full.yaml | 4 +- examples/example_16/ob08092-o4_UN.yaml | 2 +- examples/example_16/ulens_model_fit.py | 62 +++++++++++-------------- 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/examples/example_16/ob03235_2_full.yaml b/examples/example_16/ob03235_2_full.yaml index 5bfb44e93..dc1239b89 100644 --- a/examples/example_16/ob03235_2_full.yaml +++ b/examples/example_16/ob03235_2_full.yaml @@ -105,10 +105,10 @@ fitting_parameters: # basename: out_ob08092_O4_MN- # multimodal: True # evidence tolerance: 0.5 - ## UltraNest only (log directory, derived param names, show_status, + ## UltraNest only (log directory, derived parameter names, show_status, ## dlogz=0.5, frac_remain=0.01, max_num_improvement_loops=-1) # log directory: ultranest_outputs/ - # derived param names: flux_s_1 flux_b_1 + # derived parameter names: flux_s_1 flux_b_1 # show_status: True # dlogz: 2. # frac_remain: 0.5 diff --git a/examples/example_16/ob08092-o4_UN.yaml b/examples/example_16/ob08092-o4_UN.yaml index c699545ce..44a9d6a3a 100644 --- a/examples/example_16/ob08092-o4_UN.yaml +++ b/examples/example_16/ob08092-o4_UN.yaml @@ -7,7 +7,7 @@ prior_limits: t_E: 16. 19.6 fitting_parameters: log directory: ultranest_outputs/ - derived param names: flux_s_1 flux_b_1 + derived parameter names: flux_s_1 flux_b_1 show_status: True n_live_points: 1000 # `n_live_points` can also be named `min_num_live_points` diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index c93b2c230..d2a392536 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -234,7 +234,7 @@ class UlensModelFit(object): there is a check if directory exists. If not given, no outputs are saved. - ``derived parameters names`` (*str*) - names of additional derived + ``derived parameter names`` (*str*) - names of additional derived parameters created by transform. In microlensing, they are usually the source(s) and blending fluxes. If not given, they are ignored in the transform function. @@ -578,7 +578,7 @@ def _guess_MultiNest_or_UltraNest(self): if all([key in args_MultiNest for key in self._fitting_parameters]): return "MultiNest" - args_UltraNest = ['log directory', 'derived parameters names', + args_UltraNest = ['log directory', 'derived parameter names', 'show_status', 'dlogz', 'frac_remain', 'max_num_improvement_loops', 'n_live_points'] if all([key in args_UltraNest for key in self._fitting_parameters]): @@ -1481,7 +1481,7 @@ def _parse_fitting_parameters_UltraNest(self): ints = ['min_num_live_points', 'max_num_improvement_loops'] if 'n_live_points' in settings: ints[0] = 'n_live_points' - strings = ['log directory', 'derived parameters names'] + strings = ['log directory', 'derived parameter names'] floats = ['dlogz', 'frac_remain'] allowed = strings + bools + ints + floats @@ -1495,7 +1495,7 @@ def _parse_fitting_parameters_UltraNest(self): elif not path.isdir(self._log_dir_UltraNest): raise ValueError("log directory value in fitting_parameters" "exists, but it is a file.") - value = settings.pop("derived parameters names", "") + value = settings.pop("derived parameter names", "") self._derived_param_names_UltraNest = value.split() keys = {"n_live_points": "min_num_live_points"} @@ -2584,28 +2584,34 @@ def _setup_fit_UltraNest(self): self._flux_names = self._get_fluxes_names_to_print() self._derived_param_names_UltraNest = self._flux_names + n_dims = self._n_fit_parameters + n_params = n_dims + self._n_fluxes_per_dataset * self._return_fluxes + t_kwargs = {'n_dims': n_dims, 'n_params': n_params} self._sampler = ultranest.ReactiveNestedSampler( - self._fit_parameters, - self._ln_like, transform=self._transform_unit_cube_UltraNest, + self._fit_parameters, self._ln_like, + transform=lambda cube: self._transform_unit_cube(cube, **t_kwargs), derived_param_names=self._derived_param_names_UltraNest, log_dir=self._log_dir_UltraNest ) def _transform_unit_cube(self, cube, n_dims, n_params): """ - Transform MulitNest unit cube to microlensing parameters. + Transform MultiNest/UltraNest unit cube to microlensing parameters. - Based on SafePrior() in + MultiNest: based on SafePrior() in https://github.com/JohannesBuchner/PyMultiNest/blob/master/ pymultinest/solve.py + UltraNest: based on the above and UltraNest documentation + https://johannesbuchner.github.io/UltraNest/example-sine-line.html NOTE: We call self._ln_like() here (and remember the result) because in MultiNest you can add fluxes only in "prior" function, not in likelihood function. """ cube_out = self._min_values + cube[:n_dims] * self._range_values - for i in range(n_dims): - cube[i] = cube_out[i] + if self._fit_method == "MultiNest": + for i in range(n_dims): + cube[i] = cube_out[i] if "x_caustic_in" in self._model.parameters.parameters: self._set_model_parameters(cube_out) @@ -2613,19 +2619,25 @@ def _transform_unit_cube(self, cube, n_dims, n_params): self._last_ln_like = -1.e300 self._last_theta = cube_out if self._return_fluxes: - for i in range(n_dims, n_params): - cube[i] = 0. self._last_fluxes = np.zeros(n_params - n_dims) - return + if self._fit_method == "MultiNest": + for i in range(n_dims, n_params): + cube[i] = 0. + return + cube_out = np.append(cube_out, self._last_fluxes) + return cube_out self._last_ln_like = self._ln_like(cube_out) self._last_theta = cube_out if self._return_fluxes: fluxes = self._get_fluxes() + self._last_fluxes = fluxes + if self._fit_method == "UltraNest": + cube_out = np.append(cube_out, fluxes) + return cube_out for i in range(n_dims, n_params): cube[i] = fluxes[i-n_dims] - self._last_fluxes = fluxes def _ln_like_MN(self, theta, n_dim, n_params, lnew): """ @@ -2654,28 +2666,6 @@ def _ln_like_MN(self, theta, n_dim, n_params, lnew): return ln_like - def _transform_unit_cube_UltraNest(self, cube): - """ - Transform UltraNest unit cube to microlensing parameters. - - Based on _transform_unit_cube() for MultiNest method and tutorials - from UltraNest documentation: - https://johannesbuchner.github.io/UltraNest/example-sine-line.html - """ - n_dims = self._n_fit_parameters - n_params = n_dims + self._n_fluxes_per_dataset * self._return_fluxes - cube_out = self._min_values + cube[:n_dims] * self._range_values - - # TBD: add "x_caustic_in" checks as in line 2610... - - if self._return_fluxes: - fluxes = self._get_fluxes() - for i in range(n_dims, n_params): - cube_out = np.append(cube_out, fluxes[i-n_dims]) - self._last_fluxes = fluxes - - return cube_out - def _run_fit(self): """ Call the method that does the fit. From 31e2cfb222873dcdd1e17e24227e5f71119cdaaf Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Tue, 17 Sep 2024 12:48:11 +0200 Subject: [PATCH 17/21] Added prior in blending_flux and t_E for UltraNest --- examples/example_16/ob08092-o4_UN.yaml | 4 +++ examples/example_16/ulens_model_fit.py | 43 ++++++++++++++++++-------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/examples/example_16/ob08092-o4_UN.yaml b/examples/example_16/ob08092-o4_UN.yaml index 44a9d6a3a..dba6bb2d6 100644 --- a/examples/example_16/ob08092-o4_UN.yaml +++ b/examples/example_16/ob08092-o4_UN.yaml @@ -5,6 +5,10 @@ prior_limits: t_0: [2455379.4, 2455379.76] u_0: [0.3, 0.65] t_E: 16. 19.6 +fit_constraints: + negative_blending_flux_sigma_mag: 20. + prior: + t_E: Mroz et al. 2020 fitting_parameters: log directory: ultranest_outputs/ derived parameter names: flux_s_1 flux_b_1 diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index d2a392536..d29769e47 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -1595,7 +1595,6 @@ def _parse_fit_constraints(self): """ Parse the fitting constraints that are not simple limits on parameters """ - self._check_fit_constraints() self._prior_t_E = None self._priors = None @@ -1603,6 +1602,7 @@ def _parse_fit_constraints(self): self._set_default_fit_constraints() return + self._check_fit_constraints() self._parse_fit_constraints_keys() self._parse_fit_constraints_fluxes() self._parse_fit_constraints_posterior() @@ -1614,11 +1614,22 @@ def _check_fit_constraints(self): """ Run checks on self._fit_constraints """ - lst_check = ['MultiNest', 'UltraNest'] - if self._fit_constraints is not None and self._fit_method in lst_check: - msg = "Currently no fit_constraints are implemented for {:} " + \ - "fit. Please contact Radek Poleski with a specific request." - raise NotImplementedError(msg.format(self._fit_method)) + if self._fit_method == 'MultiNest': + raise NotImplementedError( + "Currently no fit_constraints are implemented for MultiNest " + "fit. Please contact Radek Poleski with a specific request.") + + if self._fit_method == 'UltraNest': + allowed_keys = {'negative_blending_flux_sigma_mag', 'prior'} + used_keys = set(self._fit_constraints.keys()) + if not used_keys.issubset(allowed_keys): + raise NotImplementedError( + "The supported fit_constraints options for UltraNest are" + " `negative_blending_flux_sigma_mag` and `prior`.") + if 'prior' in used_keys: + if set(self._fit_constraints['prior'].keys()) != {'t_E'}: + raise ValueError( + "Only `t_E` is allowed in fit_constraints['prior'].") if isinstance(self._fit_constraints, list): raise TypeError( @@ -2233,9 +2244,12 @@ def _ln_prob(self, theta): NOTE: we're using np.log(), i.e., natural logarithms. """ - ln_prior = self._ln_prior(theta) - if not np.isfinite(ln_prior): - return self._return_ln_prob(-np.inf) + if self._fit_method == "EMCEE": + ln_prior = self._ln_prior(theta) + if not np.isfinite(ln_prior): + return self._return_ln_prob(-np.inf) + elif self._fit_method == "UltraNest": + ln_prior = self._ln_prior_t_E() if self._prior_t_E else 0. ln_like = self._ln_like(theta) if not np.isfinite(ln_like): @@ -2251,9 +2265,11 @@ def _ln_prob(self, theta): ln_prob += ln_prior_flux - self._update_best_model_EMCEE(ln_prob, theta, fluxes) + if self._fit_method == "EMCEE": + self._update_best_model_EMCEE(ln_prob, theta, fluxes) + return self._return_ln_prob(ln_prob, fluxes) - return self._return_ln_prob(ln_prob, fluxes) + return ln_prob def _return_ln_prob(self, value, fluxes=None): """ @@ -2487,7 +2503,8 @@ def _run_flux_checks_ln_prior(self, fluxes): return outside inside += self._apply_negative_blending_flux_sigma_mag_prior(fluxes) - inside += self._apply_color_prior(fluxes) + if self._fit_method == "EMCEE": + inside += self._apply_color_prior(fluxes) return inside @@ -2588,7 +2605,7 @@ def _setup_fit_UltraNest(self): n_params = n_dims + self._n_fluxes_per_dataset * self._return_fluxes t_kwargs = {'n_dims': n_dims, 'n_params': n_params} self._sampler = ultranest.ReactiveNestedSampler( - self._fit_parameters, self._ln_like, + self._fit_parameters, self._ln_prob, transform=lambda cube: self._transform_unit_cube(cube, **t_kwargs), derived_param_names=self._derived_param_names_UltraNest, log_dir=self._log_dir_UltraNest From d0376cf23191e0d147027c5e2f2a8febecb9839a Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Tue, 17 Sep 2024 12:50:12 +0200 Subject: [PATCH 18/21] Declare _parse_posterior_abs when posterior parsing is not used --- examples/example_16/ulens_model_fit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index d29769e47..fc929a8d4 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -1800,6 +1800,7 @@ def _parse_fit_constraints_posterior(self): Parse constraints on what is done with posterior. """ if 'posterior parsing' not in self._fit_constraints: + self._parse_posterior_abs = list() return if self._fit_method != "EMCEE": From adad7697d9dd9454bf321d793c3fffa703094b21 Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Tue, 17 Sep 2024 15:37:32 +0200 Subject: [PATCH 19/21] Added check of derived parameter names for UltraNest --- examples/example_16/ulens_model_fit.py | 30 +++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index fc929a8d4..54b9e68fe 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -706,7 +706,6 @@ def run_fit(self): self._check_ulens_model_parameters() self._get_parameters_ordered() self._get_parameters_latex() - self._parse_fitting_parameters() self._set_prior_limits() self._parse_fit_constraints() if self._fit_method == "EMCEE": @@ -714,6 +713,7 @@ def run_fit(self): self._check_fixed_parameters() self._make_model_and_event() + self._parse_fitting_parameters() if self._fit_method == "EMCEE": self._get_starting_parameters() @@ -1488,15 +1488,9 @@ def _parse_fitting_parameters_UltraNest(self): self._check_required_and_allowed_parameters(required, allowed) self._check_parameters_types(settings, bools, ints, floats, strings) self._log_dir_UltraNest = settings.pop("log directory", None) - if self._log_dir_UltraNest is not None: - if not path.exists(self._log_dir_UltraNest): - raise ValueError("log directory value in fitting_parameters" - "does not exist.") - elif not path.isdir(self._log_dir_UltraNest): - raise ValueError("log directory value in fitting_parameters" - "exists, but it is a file.") value = settings.pop("derived parameter names", "") self._derived_param_names_UltraNest = value.split() + self._check_dir_and_parameter_names_Ultranest() keys = {"n_live_points": "min_num_live_points"} same_keys = ["min_num_live_points", 'max_num_improvement_loops', @@ -1504,6 +1498,26 @@ def _parse_fitting_parameters_UltraNest(self): keys = {**keys, **{key: key for key in same_keys}} self._set_dict_safely(self._kwargs_UltraNest, settings, keys) + def _check_dir_and_parameter_names_Ultranest(self): + """ + Checks if the path to `log directory` exists and is a directory, + and also if the number of `derived parameter names` matches the + number of derived fluxes. + """ + if self._log_dir_UltraNest is not None: + if not path.exists(self._log_dir_UltraNest): + raise ValueError("log directory value in fitting_parameters" + "does not exist.") + elif not path.isdir(self._log_dir_UltraNest): + raise ValueError("log directory value in fitting_parameters" + "exists, but it is a file.") + + n_datasets = len(self._datasets) + n_fluxes = self._n_fluxes_per_dataset + if len(self._derived_param_names_UltraNest) != n_datasets * n_fluxes: + raise ValueError("The number of `derived parameter names` must " + "match the number of derived fluxes.") + def _set_prior_limits(self): """ Set minimum and maximum values of the prior space From 1a211e541f53233e698702b097dbb4e89af7b257 Mon Sep 17 00:00:00 2001 From: "Raphael A. P. Oliveira" Date: Tue, 17 Sep 2024 16:25:20 +0200 Subject: [PATCH 20/21] Minor changes to full YAML file, removed _n_walkers=None --- examples/example_16/ob03235_2_full.yaml | 13 +++++++------ examples/example_16/ulens_model_fit.py | 6 ++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/examples/example_16/ob03235_2_full.yaml b/examples/example_16/ob03235_2_full.yaml index dc1239b89..23780c8e1 100644 --- a/examples/example_16/ob03235_2_full.yaml +++ b/examples/example_16/ob03235_2_full.yaml @@ -7,11 +7,12 @@ photometry_files: # - {..., add_2450000: False} fit_method: EMCEE # Options: EMCEE, MultiNest, UltraNest. -# If not given, EMCEE is used if starting_parameters are given; if prior_limits -# are given, MultiNest or UltraNest is used depending on fitting_parameters. -# If MultiNest/UltraNest is selected: starting_parameters, fit_constraints, -# min_values and max_value cannot be provided. The trace plot cannot be -# generated as well. +# If fit_method is not given, EMCEE is used if starting_parameters are given; +# and MultiNest or UltraNest is used if prior_limits are given, depending on +# the combination of fitting_parameters. +# If MultiNest/UltraNest is used, this file cannot have starting_parameters, +# fit_constraints (except negative_blending_flux_sigma_mag and prior->t_E for +# UltraNest), min_values, max_values and plots->trace. model: methods: 2452800. point_source 2452833. VBBL 2452845. point_source 2452860. default method: point_source_point_lens @@ -108,7 +109,7 @@ fitting_parameters: ## UltraNest only (log directory, derived parameter names, show_status, ## dlogz=0.5, frac_remain=0.01, max_num_improvement_loops=-1) # log directory: ultranest_outputs/ - # derived parameter names: flux_s_1 flux_b_1 + # derived parameter names: flux_s_1 flux_b_1 flux_s_2 flux_b_2 # show_status: True # dlogz: 2. # frac_remain: 0.5 diff --git a/examples/example_16/ulens_model_fit.py b/examples/example_16/ulens_model_fit.py index 54b9e68fe..5bdfe6d28 100644 --- a/examples/example_16/ulens_model_fit.py +++ b/examples/example_16/ulens_model_fit.py @@ -1372,11 +1372,9 @@ def _get_n_walkers(self): if 'n_walkers' in self._fitting_parameters: self._n_walkers = self._fitting_parameters['n_walkers'] else: - if self._starting_parameters_type == 'file': - self._n_walkers = None - elif self._starting_parameters_type == 'draw': + if self._starting_parameters_type == 'draw': self._n_walkers = 4 * self._n_fit_parameters - else: + elif self._starting_parameters_type != 'file': raise ValueError( 'Unexpected: ' + self._starting_parameters_type) From d45acf24ffa7d76b9846ae0074a4e218b74af58f Mon Sep 17 00:00:00 2001 From: radek_poleski Date: Sun, 22 Sep 2024 17:11:14 +0200 Subject: [PATCH 21/21] ex16 - minor --- examples/example_16/ob08092-o4_UN.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example_16/ob08092-o4_UN.yaml b/examples/example_16/ob08092-o4_UN.yaml index dba6bb2d6..2cf189140 100644 --- a/examples/example_16/ob08092-o4_UN.yaml +++ b/examples/example_16/ob08092-o4_UN.yaml @@ -10,7 +10,7 @@ fit_constraints: prior: t_E: Mroz et al. 2020 fitting_parameters: - log directory: ultranest_outputs/ + log directory: outputs_ultranest/ derived parameter names: flux_s_1 flux_b_1 show_status: True n_live_points: 1000