redback 1.0.1__py3-none-any.whl → 1.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. redback/__init__.py +4 -0
  2. redback/constraints.py +46 -25
  3. redback/eos.py +1 -0
  4. redback/get_data/fink.py +1 -1
  5. redback/get_data/lasair.py +3 -4
  6. redback/get_data/swift.py +7 -7
  7. redback/interaction_processes.py +1 -4
  8. redback/likelihoods.py +207 -21
  9. redback/model_library.py +2 -2
  10. redback/plotting.py +10 -10
  11. redback/priors/bazin_sne.prior +5 -0
  12. redback/priors/csm_interaction.prior +6 -7
  13. redback/priors/csm_nickel.prior +3 -3
  14. redback/priors/csm_shock_and_arnett.prior +11 -0
  15. redback/priors/csm_shock_and_arnett_bolometric.prior +10 -0
  16. redback/priors/csm_shock_breakout.prior +7 -0
  17. redback/priors/nicholl_bns.prior +2 -1
  18. redback/priors/one_comp_kne_rosswog_heatingrate.prior +5 -0
  19. redback/priors/pwn.prior +7 -0
  20. redback/priors/shocked_cocoon.prior +6 -6
  21. redback/priors/sn_fallback.prior +8 -0
  22. redback/priors/stream_stream_tde.prior +10 -0
  23. redback/priors/stream_stream_tde_bolometric.prior +9 -0
  24. redback/priors/tde_analytical.prior +5 -5
  25. redback/priors/tde_analytical_bolometric.prior +6 -4
  26. redback/priors/tde_fallback.prior +9 -0
  27. redback/priors/tde_fallback_bolometric.prior +6 -0
  28. redback/priors/tde_synchrotron.prior +6 -0
  29. redback/priors/tophat_from_emulator.prior +9 -0
  30. redback/priors/two_comp_kne_rosswog_heatingrate.prior +9 -0
  31. redback/priors/two_layer_stratified_kilonova.prior +1 -1
  32. redback/priors/villar_sne.prior +7 -0
  33. redback/priors.py +12 -1
  34. redback/sed.py +194 -2
  35. redback/simulate_transients.py +71 -35
  36. redback/tables/GRBs_w_redshift.txt +430 -413
  37. redback/tables/LGRB_table.txt +70 -6
  38. redback/tables/SGRB_table.txt +139 -135
  39. redback/tables/filters.csv +14 -0
  40. redback/tables/qdot_rosswogkorobkin24.pck +0 -0
  41. redback/tables/ztf.tar.gz +0 -0
  42. redback/transient/afterglow.py +17 -7
  43. redback/transient/kilonova.py +6 -3
  44. redback/transient/prompt.py +14 -4
  45. redback/transient/supernova.py +7 -3
  46. redback/transient/tde.py +6 -3
  47. redback/transient/transient.py +29 -12
  48. redback/transient_models/afterglow_models.py +152 -146
  49. redback/transient_models/combined_models.py +69 -48
  50. redback/transient_models/extinction_models.py +6 -6
  51. redback/transient_models/general_synchrotron_models.py +518 -0
  52. redback/transient_models/integrated_flux_afterglow_models.py +2 -2
  53. redback/transient_models/kilonova_models.py +310 -61
  54. redback/transient_models/magnetar_driven_ejecta_models.py +2 -2
  55. redback/transient_models/magnetar_models.py +1 -1
  56. redback/transient_models/phenomenological_models.py +69 -1
  57. redback/transient_models/shock_powered_models.py +159 -110
  58. redback/transient_models/supernova_models.py +211 -43
  59. redback/transient_models/tde_models.py +975 -5
  60. redback/utils.py +309 -16
  61. {redback-1.0.1.dist-info → redback-1.0.3.dist-info}/METADATA +46 -6
  62. {redback-1.0.1.dist-info → redback-1.0.3.dist-info}/RECORD +65 -49
  63. {redback-1.0.1.dist-info → redback-1.0.3.dist-info}/WHEEL +1 -1
  64. redback/tables/ztf_obslog.csv +0 -106649
  65. {redback-1.0.1.dist-info → redback-1.0.3.dist-info}/LICENCE.md +0 -0
  66. {redback-1.0.1.dist-info → redback-1.0.3.dist-info}/top_level.txt +0 -0
redback/__init__.py CHANGED
@@ -2,3 +2,7 @@ from redback import analysis, constants, get_data, redback_errors, priors, resul
2
2
  transient_models, utils, photosphere, sed, interaction_processes, constraints, plotting, model_library, simulate_transients
3
3
  from redback.transient import afterglow, kilonova, prompt, supernova, tde
4
4
  from redback.sampler import fit_model
5
+ from redback.utils import setup_logger
6
+
7
+ __version__ = "1.0.2"
8
+ setup_logger(log_level='info')
redback/constraints.py CHANGED
@@ -2,6 +2,7 @@ import numpy as np
2
2
  import redback.eos as eos
3
3
  from redback.constants import *
4
4
  from redback.utils import calc_tfb
5
+ from scipy.interpolate import interp1d
5
6
 
6
7
  def slsn_constraint(parameters):
7
8
  """
@@ -23,7 +24,7 @@ def slsn_constraint(parameters):
23
24
  neutrino_energy = 1e51
24
25
  total_energy = kinetic_energy + neutrino_energy
25
26
  # ensure rotational energy is greater than total output energy
26
- converted_parameters['erot_constraint'] = rotational_energy - total_energy
27
+ converted_parameters['erot_constraint'] = total_energy/rotational_energy
27
28
  # ensure t_nebula is greater than 100 days
28
29
  converted_parameters['t_nebula_min'] = tnebula - 100
29
30
  return converted_parameters
@@ -43,7 +44,7 @@ def basic_magnetar_powered_sn_constraints(parameters):
43
44
  kinetic_energy = 0.5 * mej * vej**2
44
45
  rotational_energy = 2.6e52 * (mass_ns/1.4)**(3./2.) * p0**(-2)
45
46
  # ensure rotational energy is greater than total output energy
46
- converted_parameters['erot_constraint'] = rotational_energy - kinetic_energy
47
+ converted_parameters['erot_constraint'] = kinetic_energy/rotational_energy
47
48
  return converted_parameters
48
49
 
49
50
  def general_magnetar_powered_sn_constraints(parameters):
@@ -61,9 +62,24 @@ def general_magnetar_powered_sn_constraints(parameters):
61
62
  tau = parameters['tsd']
62
63
  rotational_energy = 2*l0*tau
63
64
  # ensure rotational energy is greater than total output energy
64
- converted_parameters['erot_constraint'] = rotational_energy - kinetic_energy
65
+ converted_parameters['erot_constraint'] = kinetic_energy/rotational_energy
65
66
  return converted_parameters
66
67
 
68
+ def vacuum_dipole_magnetar_powered_supernova_constraints(parameters):
69
+ """
70
+ Constraint so that magnetar rotational energy is smaller than some number
71
+
72
+ :param parameters: dictionary of parameters
73
+ :return: converted_parameters dictionary where the violated samples are thrown out
74
+ """
75
+ converted_parameters = parameters.copy()
76
+ l0 = parameters['l0']
77
+ tau = parameters['tau_sd']
78
+ rotational_energy = l0*tau
79
+ # ensure rotational energy is less than the maximum spin down energy
80
+ converted_parameters['erot_constraint'] = rotational_energy/1e53
81
+ return converted_parameters
82
+
67
83
  def general_magnetar_powered_supernova_constraints(parameters):
68
84
  """
69
85
  Constraint so that magnetar rotational energy is smaller than some number
@@ -77,7 +93,7 @@ def general_magnetar_powered_supernova_constraints(parameters):
77
93
  nn = parameters['nn']
78
94
  rotational_energy = (nn-1)*l0*tau/2.0
79
95
  # ensure rotational energy is less than the maximum spin down energy
80
- converted_parameters['erot_constraint'] = 1e53 - rotational_energy
96
+ converted_parameters['erot_constraint'] = rotational_energy/1e53
81
97
  return converted_parameters
82
98
 
83
99
  def tde_constraints(parameters):
@@ -91,7 +107,7 @@ def tde_constraints(parameters):
91
107
  rp = parameters['pericenter_radius']
92
108
  mass_bh = parameters['mass_bh']
93
109
  schwarzchild_radius = (2 * graviational_constant * mass_bh * solar_mass /(speed_of_light**2))/au_cgs
94
- converted_parameters['disruption_radius'] = rp - schwarzchild_radius
110
+ converted_parameters['disruption_radius'] = schwarzchild_radius/rp
95
111
  return converted_parameters
96
112
 
97
113
  def gaussianrise_tde_constraints(parameters):
@@ -103,13 +119,11 @@ def gaussianrise_tde_constraints(parameters):
103
119
  converted_parameters = parameters.copy()
104
120
  ms = parameters['stellar_mass']
105
121
  mbh6 = parameters['mbh_6']
106
- etamin = 0.01*(ms**(-7./15.))*(mbh6**(2./3.))
107
122
  betamax = 12.*(ms**(7./15.))*(mbh6**(-2./3.))
108
123
  tfb = calc_tfb(binding_energy_const=0.8, mbh_6=mbh6,stellar_mass=ms)/86400
109
124
  tfb_obs = tfb * (1 + parameters['redshift'])
110
- converted_parameters['eta_low'] = converted_parameters['eta'] - etamin
111
- converted_parameters['beta_high'] = betamax - converted_parameters['beta']
112
- converted_parameters['tfb_max'] = tfb_obs - converted_parameters['peak_time']
125
+ converted_parameters['beta_high'] = converted_parameters['beta']/betamax
126
+ converted_parameters['tfb_max'] = converted_parameters['peak_time']/tfb_obs
113
127
  return converted_parameters
114
128
 
115
129
  def nuclear_burning_constraints(parameters):
@@ -126,7 +140,7 @@ def nuclear_burning_constraints(parameters):
126
140
  kinetic_energy = 0.5 * mej * (vej / 2.0) ** 2
127
141
  excess_constant = -(56.0 / 4.0 * 2.4249 - 53.9037) / proton_mass * mev_cgs
128
142
  emax = excess_constant * mej * fnickel
129
- converted_parameters['emax_constraint'] = emax - kinetic_energy
143
+ converted_parameters['emax_constraint'] = kinetic_energy/emax
130
144
  return converted_parameters
131
145
 
132
146
  def simple_fallback_constraints(parameters):
@@ -149,7 +163,7 @@ def simple_fallback_constraints(parameters):
149
163
  neutrino_energy = 1e51
150
164
  total_energy = e_fallback + neutrino_energy
151
165
  # ensure total energy is greater than kinetic energy
152
- converted_parameters['en_constraint'] = total_energy - kinetic_energy
166
+ converted_parameters['en_constraint'] = kinetic_energy/total_energy
153
167
  # ensure t_nebula is greater than 100 days
154
168
  converted_parameters['t_nebula_min'] = tnebula - 100
155
169
  return converted_parameters
@@ -162,15 +176,18 @@ def csm_constraints(parameters):
162
176
  :param parameters: dictionary of parameters
163
177
  :return: converted_parameters dictionary where the violated samples are thrown out
164
178
  """
165
- from redback.utils import get_csm_properties
166
179
  converted_parameters = parameters.copy()
167
180
  mej = parameters['mej']
168
181
  csm_mass = parameters['csm_mass']
169
182
  kappa = parameters['kappa']
170
183
  r0 = parameters['r0']
171
184
  vej = parameters['vej']
172
- nn = parameters.get('nn', np.ones(len(mej)) * 12.)
173
- delta = parameters.get('delta', np.ones(len(mej)))
185
+ if hasattr(parameters['mej'], "__len__"):
186
+ nn = parameters.get('nn', np.ones(len(mej)) * 8.)
187
+ delta = parameters.get('delta', np.ones(len(mej)))
188
+ else:
189
+ nn = parameters.get('nn', 12.)
190
+ delta = parameters.get('delta', 0.)
174
191
  eta = parameters['eta']
175
192
  rho = parameters['rho']
176
193
 
@@ -180,12 +197,16 @@ def csm_constraints(parameters):
180
197
  vej = vej * km_cgs
181
198
  Esn = 3. * vej ** 2 * mej / 10.
182
199
 
183
- AA = np.zeros(len(mej))
184
- Bf = np.zeros(len(mej))
185
- for x in range(len(mej)):
186
- csm_properties = get_csm_properties(nn[x], eta[x])
187
- AA[x] = csm_properties.AA
188
- Bf[x] = csm_properties.Bf
200
+ ns = [6, 7, 8, 9, 10, 12, 14]
201
+ Bfs = [1.377, 1.299, 1.267, 1.250, 1.239, 1.226, 1.218]
202
+ As = [0.62, 0.27, 0.15, 0.096, 0.067, 0.038, 0.025]
203
+
204
+ Bf_func = interp1d(ns, Bfs)
205
+ A_func = interp1d(ns, As)
206
+
207
+ Bf = Bf_func(nn)
208
+ AA = A_func(nn)
209
+
189
210
  qq = rho * r0 ** eta
190
211
  # outer CSM shell radius
191
212
  radius_csm = ((3.0 - eta) / (4.0 * np.pi * qq) * csm_mass + r0 ** (3.0 - eta)) ** (
@@ -203,14 +224,14 @@ def csm_constraints(parameters):
203
224
  (3.0 - delta) * (nn - 3.0) * mej) ** ((nn - 5.0) / 2.0))
204
225
 
205
226
  tshock = ((radius_csm - r0) / Bf / (AA * g_n / qq) ** (
206
- 1. / (nn - eta))) ** ((nn - eta) /(nn - 3))
227
+ 1. / (nn - eta))) ** ((nn - eta) / (nn - 3))
207
228
 
208
- diffusion_time = np.sqrt(2. * kappa * mass_csm_threshold /(vej * 13.7 * 3.e10))
229
+ diffusion_time = np.sqrt(2. * kappa * mass_csm_threshold / (vej * 13.7 * 3.e10))
209
230
  # ensure shock crossing time is greater than diffusion time
210
- # converted_parameters['shock_time'] = tshock - diffusion_time
231
+ converted_parameters['shock_time'] = diffusion_time/tshock
211
232
  # ensure photospheric radius is within the csm i.e., r_photo < radius_csm and r_photo > r0
212
- converted_parameters['photosphere_constraint_1'] = radius_csm - r_photosphere
213
- converted_parameters['photosphere_constraint_2'] = r_photosphere - r0
233
+ converted_parameters['photosphere_constraint_1'] = r_photosphere/radius_csm
234
+ converted_parameters['photosphere_constraint_2'] = r0/r_photosphere
214
235
  return converted_parameters
215
236
 
216
237
  def piecewise_polytrope_eos_constraints(parameters):
redback/eos.py CHANGED
@@ -78,6 +78,7 @@ class PiecewisePolytrope(object):
78
78
  radius, mass, k2 = lalsim.SimNeutronStarTOVODEIntegrate(central_pressure, polytrope)
79
79
 
80
80
  Lambda = (2 / 3) * k2 * (cc.c.si.value ** 2 * radius / (cc.G.si.value * mass)) ** 5
81
+ mass = mass / cc.M_sun.si.value
81
82
  return mass, Lambda
82
83
 
83
84
  def lambda_array_of_central_pressure(self, central_pressure_array, maximum_mass_lower_limit=2.01):
redback/get_data/fink.py CHANGED
@@ -45,7 +45,7 @@ class FinkDataGetter(DataGetter):
45
45
  :return: The fink raw data url.
46
46
  :rtype: str
47
47
  """
48
- return "https://fink-portal.org/api/v1/objects"
48
+ return "https://api.fink-portal.org/api/v1/objects"
49
49
 
50
50
  @property
51
51
  def objectId(self) -> str:
@@ -58,8 +58,8 @@ class LasairDataGetter(DataGetter):
58
58
  f"Are you sure you are using the right alias?")
59
59
  data = pd.read_html(self.url)
60
60
  data = data[1]
61
- data['diff_magnitude'] = [data['magpsf'].iloc[x].split(" ")[0] for x in range(len(data))]
62
- data['diff_magnitude_error'] = [data['magpsf'].iloc[x].split(" ")[-1] for x in range(len(data))]
61
+ data['diff_magnitude'] = [data['unforced mag'].iloc[x].split(" ")[0] for x in range(len(data))]
62
+ data['diff_magnitude_error'] = [data['unforced mag'].iloc[x].split(" ")[-1] for x in range(len(data))]
63
63
 
64
64
  logger.warning('Using the difference magnitude to calculate quantities. '
65
65
  'Reduce the data yourself if you would like to use a reference magnitude')
@@ -67,7 +67,6 @@ class LasairDataGetter(DataGetter):
67
67
  # Change the dataframe to the correct raw dataframe format
68
68
  del data['UTC']
69
69
  del data['images']
70
- del data['magpsf']
71
70
  data.to_csv(self.raw_file_path, index=False)
72
71
  logger.info(f"Retrieved data for {self.transient}.")
73
72
 
@@ -83,7 +82,7 @@ class LasairDataGetter(DataGetter):
83
82
  return pd.read_csv(self.processed_file_path)
84
83
 
85
84
  raw_data = pd.read_csv(self.raw_file_path)
86
- raw_data = raw_data[raw_data['difference flux status'] != 'limit']
85
+ raw_data = raw_data[raw_data['unforced mag status'] != 'limit']
87
86
  lasair_to_general_bands = {"g": "ztfg", "r": "ztfr", "i":'ztfi'}
88
87
  processed_data = pd.DataFrame()
89
88
 
redback/get_data/swift.py CHANGED
@@ -199,9 +199,9 @@ class SwiftDataGetter(GRBDataGetter):
199
199
  driver = fetch_driver()
200
200
  try:
201
201
  driver.get(self.grb_website)
202
- driver.find_element_by_xpath("//select[@name='xrtsub']/option[text()='no']").click()
202
+ driver.find_element("xpath", "//select[@name='xrtsub']/option[text()='no']").click()
203
203
  time.sleep(20)
204
- driver.find_element_by_id("xrt_DENSITY_makeDownload").click()
204
+ driver.find_element("id","xrt_DENSITY_makeDownload").click()
205
205
  time.sleep(20)
206
206
  grb_url = driver.current_url
207
207
  # scrape the data
@@ -227,19 +227,19 @@ class SwiftDataGetter(GRBDataGetter):
227
227
  # select option for BAT bin_size
228
228
  bat_binning = 'batxrtbin'
229
229
  if check_element(driver, bat_binning):
230
- driver.find_element_by_xpath("//select[@name='batxrtbin']/option[text()='SNR 4']").click()
230
+ driver.find_element("xpath", "//select[@name='batxrtbin']/option[text()='SNR 4']").click()
231
231
  # select option for subplot
232
232
  subplot = "batxrtsub"
233
233
  if check_element(driver, subplot):
234
- driver.find_element_by_xpath("//select[@name='batxrtsub']/option[text()='no']").click()
234
+ driver.find_element("xpath","//select[@name='batxrtsub']/option[text()='no']").click()
235
235
  # Select option for flux density
236
236
  flux_density1 = "batxrtband1"
237
237
  flux_density0 = "batxrtband0"
238
238
  if (check_element(driver, flux_density1)) and (check_element(driver, flux_density0)):
239
- driver.find_element_by_xpath(".//*[@id='batxrtband1']").click()
240
- driver.find_element_by_xpath(".//*[@id='batxrtband0']").click()
239
+ driver.find_element("xpath",".//*[@id='batxrtband1']").click()
240
+ driver.find_element("xpath",".//*[@id='batxrtband0']").click()
241
241
  # Generate data file
242
- driver.find_element_by_xpath(".//*[@id='batxrt_XRTBAND_makeDownload']").click()
242
+ driver.find_element("xpath",".//*[@id='batxrt_XRTBAND_makeDownload']").click()
243
243
  time.sleep(20)
244
244
  grb_url = driver.current_url
245
245
  driver.quit()
@@ -165,8 +165,6 @@ class CSMDiffusion(object):
165
165
  # photosphere radius
166
166
  r_photosphere = self.r_photosphere
167
167
 
168
- tau_diff = (self.kappa * self.csm_mass) / (13.8 * speed_of_light * r_photosphere) / day_to_s
169
-
170
168
  # mass of the optically thick CSM (tau > 2/3).
171
169
  mass_csm_threshold = self.mass_csm_threshold
172
170
 
@@ -180,7 +178,7 @@ class CSMDiffusion(object):
180
178
  lu = len(uniq_times)
181
179
 
182
180
  num = int(round(timesteps / 2.0))
183
- lsp = np.logspace(np.log10(tau_diff /self.dense_times[-1]) + minimum_log_spacing, 0, num)
181
+ lsp = np.logspace(np.log10(t0 /self.dense_times[-1]) + minimum_log_spacing, 0, num)
184
182
  xm = np.unique(np.concatenate((lsp, 1 - lsp)))
185
183
 
186
184
  int_times = tb + (uniq_times.reshape(lu, 1) - tb) * xm
@@ -192,7 +190,6 @@ class CSMDiffusion(object):
192
190
 
193
191
  uniq_lums = np.trapz(int_args, int_times, axis=1)
194
192
  uniq_lums *= np.exp(-int_tes/t0)/t0
195
-
196
193
  new_lums = uniq_lums[np.searchsorted(uniq_times, self.time)]
197
194
  return new_lums
198
195
 
redback/likelihoods.py CHANGED
@@ -3,11 +3,13 @@ from typing import Any, Union
3
3
 
4
4
  import bilby
5
5
  from scipy.special import gammaln
6
-
6
+ from redback.utils import logger
7
+ from bilby.core.prior import DeltaFunction, Constraint
7
8
 
8
9
  class _RedbackLikelihood(bilby.Likelihood):
9
10
 
10
- def __init__(self, x: np.ndarray, y: np.ndarray, function: callable, kwargs: dict = None) -> None:
11
+ def __init__(self, x: np.ndarray, y: np.ndarray, function: callable, kwargs: dict = None, priors=None,
12
+ fiducial_parameters=None) -> None:
11
13
  """
12
14
 
13
15
  :param x: The x values.
@@ -18,11 +20,19 @@ class _RedbackLikelihood(bilby.Likelihood):
18
20
  :type function: callable
19
21
  :param kwargs: Any additional keywords for 'function'.
20
22
  :type kwargs: Union[dict, None]
23
+ :param priors: The priors for the parameters. Default to None if not provided.
24
+ Only necessary if using maximum likelihood estimation functionality.
25
+ :type priors: Union[dict, None]
26
+ :param fiducial_parameters: The starting guesses for model parameters to
27
+ use in the optimization for maximum likelihood estimation. Default to None if not provided.
28
+ :type fiducial_parameters: Union[dict, None]
21
29
  """
22
30
  self.x = x
23
31
  self.y = y
24
32
  self.function = function
25
33
  self.kwargs = kwargs
34
+ self.priors = priors
35
+ self.fiducial_parameters = fiducial_parameters
26
36
 
27
37
  parameters = bilby.core.utils.introspection.infer_parameters_from_function(func=function)
28
38
  super().__init__(parameters=dict.fromkeys(parameters))
@@ -46,11 +56,81 @@ class _RedbackLikelihood(bilby.Likelihood):
46
56
  """
47
57
  return len(self.x)
48
58
 
59
+ @property
60
+ def parameters_to_be_updated(self):
61
+ if self.priors is None:
62
+ return None
63
+ else:
64
+ parameters_to_be_updated = [key for key in self.priors if not isinstance(
65
+ self.priors[key], (DeltaFunction, Constraint, float, int))]
66
+ return parameters_to_be_updated
67
+
68
+ def get_parameter_dictionary_from_list(self, parameter_list):
69
+ parameter_dictionary = dict(zip(self.parameters_to_be_updated, parameter_list))
70
+ excluded_parameter_keys = set(self.fiducial_parameters) - set(self.parameters_to_be_updated)
71
+ for key in excluded_parameter_keys:
72
+ parameter_dictionary[key] = self.fiducial_parameters[key]
73
+ return parameter_dictionary
74
+
75
+ def get_parameter_list_from_dictionary(self, parameter_dict):
76
+ return [parameter_dict[k] for k in self.parameters_to_be_updated]
77
+
78
+ def get_bounds_from_priors(self, priors):
79
+ bounds = []
80
+ for key in self.parameters_to_be_updated:
81
+ bounds.append([priors[key].minimum, priors[key].maximum])
82
+ return bounds
83
+
84
+ def lnlike_scipy_maximize(self, parameter_list):
85
+ self.parameters.update(self.get_parameter_dictionary_from_list(parameter_list))
86
+ return -self.log_likelihood()
87
+
88
+ def find_maximum_likelihood_parameters(self, iterations=5, maximization_kwargs=None, method='Nelder-Mead',
89
+ break_threshold=1e-3):
90
+ """
91
+ Estimate the maximum likelihood
92
+
93
+ :param iterations: Iterations to run the minimizer for before stopping. Default is 5.
94
+ :param maximization_kwargs: Any extra keyword arguments passed to the scipy minimize function
95
+ :param method: Minimize method to use. Default is 'Nelder-Mead'
96
+ :param break_threshold: The threshold for the difference in log likelihood to break the loop. Default is 1e-3.
97
+ :return: Dictionary of maximum likelihood parameters
98
+ """
99
+ from scipy.optimize import minimize
100
+ parameter_bounds = self.get_bounds_from_priors(self.priors)
101
+ if self.priors is None:
102
+ raise ValueError("Priors must be provided to use this functionality")
103
+ if maximization_kwargs is None:
104
+ maximization_kwargs = dict()
105
+ self.parameters.update(self.fiducial_parameters)
106
+ self.parameters["fiducial"] = 0
107
+ updated_parameters_list = self.get_parameter_list_from_dictionary(self.fiducial_parameters)
108
+ old_fiducial_ln_likelihood = self.log_likelihood()
109
+ for it in range(iterations):
110
+ logger.info(f"Optimizing fiducial parameters. Iteration : {it + 1}")
111
+ output = minimize(
112
+ self.lnlike_scipy_maximize,
113
+ x0=updated_parameters_list,
114
+ bounds=parameter_bounds,
115
+ method=method,
116
+ **maximization_kwargs,)
117
+ updated_parameters_list = output['x']
118
+ updated_parameters = self.get_parameter_dictionary_from_list(updated_parameters_list)
119
+ self.parameters.update(updated_parameters)
120
+ new_fiducial_ln_likelihood = self.log_likelihood_ratio()
121
+ logger.info(f"Current lnlikelihood: {new_fiducial_ln_likelihood:.2f}")
122
+ logger.info(f"Updated parameters: {updated_parameters}")
123
+ if new_fiducial_ln_likelihood - old_fiducial_ln_likelihood < break_threshold:
124
+ break
125
+ old_fiducial_ln_likelihood = new_fiducial_ln_likelihood
126
+ return updated_parameters
127
+
49
128
 
50
129
  class GaussianLikelihood(_RedbackLikelihood):
51
130
  def __init__(
52
131
  self, x: np.ndarray, y: np.ndarray, sigma: Union[float, None, np.ndarray],
53
- function: callable, kwargs: dict = None) -> None:
132
+ function: callable, kwargs: dict = None, priors=None,
133
+ fiducial_parameters=None) -> None:
54
134
  """A general Gaussian likelihood - the parameters are inferred from the arguments of function.
55
135
 
56
136
  :param x: The x values.
@@ -67,10 +147,17 @@ class GaussianLikelihood(_RedbackLikelihood):
67
147
  :type function: callable
68
148
  :param kwargs: Any additional keywords for 'function'.
69
149
  :type kwargs: dict
150
+ :param priors: The priors for the parameters. Default to None if not provided.
151
+ Only necessary if using maximum likelihood estimation functionality.
152
+ :type priors: Union[dict, None]
153
+ :param fiducial_parameters: The starting guesses for model parameters to
154
+ use in the optimization for maximum likelihood estimation. Default to None if not provided.
155
+ :type fiducial_parameters: Union[dict, None]
70
156
  """
71
157
 
72
158
  self._noise_log_likelihood = None
73
- super().__init__(x=x, y=y, function=function, kwargs=kwargs)
159
+ super().__init__(x=x, y=y, function=function, kwargs=kwargs, priors=priors,
160
+ fiducial_parameters=fiducial_parameters)
74
161
  self.sigma = sigma
75
162
  if self.sigma is None:
76
163
  self.parameters['sigma'] = None
@@ -121,7 +208,8 @@ class GaussianLikelihood(_RedbackLikelihood):
121
208
  class GaussianLikelihoodUniformXErrors(GaussianLikelihood):
122
209
  def __init__(
123
210
  self, x: np.ndarray, y: np.ndarray, sigma: Union[float, None, np.ndarray],
124
- bin_size: Union[float, None, np.ndarray], function: callable, kwargs: dict = None) -> None:
211
+ bin_size: Union[float, None, np.ndarray], function: callable, kwargs: dict = None, priors=None,
212
+ fiducial_parameters=None) -> None:
125
213
  """A general Gaussian likelihood with uniform errors in x- the parameters are inferred from the
126
214
  arguments of function. Takes into account the X errors with a Uniform likelihood between the
127
215
  bin high and bin low values. Note that the prior for the true x values must be uniform in this range!
@@ -142,9 +230,16 @@ class GaussianLikelihoodUniformXErrors(GaussianLikelihood):
142
230
  :type function: callable
143
231
  :param kwargs: Any additional keywords for 'function'.
144
232
  :type kwargs: dict
233
+ :param priors: The priors for the parameters. Default to None if not provided.
234
+ Only necessary if using maximum likelihood estimation functionality.
235
+ :type priors: Union[dict, None]
236
+ :param fiducial_parameters: The starting guesses for model parameters to
237
+ use in the optimization for maximum likelihood estimation. Default to None if not provided.
238
+ :type fiducial_parameters: Union[dict, None]
145
239
  """
146
240
 
147
- super().__init__(x=x, y=y, sigma=sigma, function=function, kwargs=kwargs)
241
+ super().__init__(x=x, y=y, sigma=sigma, function=function, kwargs=kwargs, priors=priors,
242
+ fiducial_parameters=fiducial_parameters)
148
243
  self.xerr = bin_size * np.ones(self.n)
149
244
 
150
245
  def noise_log_likelihood(self) -> float:
@@ -183,7 +278,7 @@ class GaussianLikelihoodUniformXErrors(GaussianLikelihood):
183
278
  class GaussianLikelihoodQuadratureNoise(GaussianLikelihood):
184
279
  def __init__(
185
280
  self, x: np.ndarray, y: np.ndarray, sigma_i: Union[float, None, np.ndarray],
186
- function: callable, kwargs: dict = None) -> None:
281
+ function: callable, kwargs: dict = None, priors=None, fiducial_parameters=None) -> None:
187
282
  """
188
283
  A general Gaussian likelihood - the parameters are inferred from the
189
284
  arguments of function
@@ -205,7 +300,8 @@ class GaussianLikelihoodQuadratureNoise(GaussianLikelihood):
205
300
  """
206
301
  self.sigma_i = sigma_i
207
302
  # These lines of code infer the parameters from the provided function
208
- super().__init__(x=x, y=y, sigma=sigma_i, function=function, kwargs=kwargs)
303
+ super().__init__(x=x, y=y, sigma=sigma_i, function=function, kwargs=kwargs, priors=priors,
304
+ fiducial_parameters=fiducial_parameters)
209
305
 
210
306
  @property
211
307
  def full_sigma(self) -> Union[float, np.ndarray]:
@@ -231,19 +327,80 @@ class GaussianLikelihoodQuadratureNoise(GaussianLikelihood):
231
327
  """
232
328
  return np.nan_to_num(self._gaussian_log_likelihood(res=self.residual, sigma=self.full_sigma))
233
329
 
330
+ class GaussianLikelihoodWithFractionalNoise(GaussianLikelihood):
331
+ def __init__(
332
+ self, x: np.ndarray, y: np.ndarray, sigma_i: Union[float, None, np.ndarray],
333
+ function: callable, kwargs: dict = None, priors=None, fiducial_parameters=None) -> None:
334
+ """
335
+ A Gaussian likelihood with noise that is proportional to the model.
336
+ The parameters are inferred from the arguments of function
337
+
338
+ :param x: The x values.
339
+ :type x: np.ndarray
340
+ :param y: The y values.
341
+ :type y: np.ndarray
342
+ :param sigma_i: The standard deviation of the noise. This is part of the full noise.
343
+ The sigma used in the likelihood is sigma = sqrt(sigma_i^2*model_y**2)
344
+ :type sigma_i: Union[float, None, np.ndarray]
345
+ :param function:
346
+ The python function to fit to the data. Note, this must take the
347
+ dependent variable as its first argument. The other arguments
348
+ will require a prior and will be sampled over (unless a fixed
349
+ value is given).
350
+ :type function: callable
351
+ :param kwargs: Any additional keywords for 'function'.
352
+ :type kwargs: dict
353
+ :param priors: The priors for the parameters. Default to None if not provided.
354
+ Only necessary if using maximum likelihood estimation functionality.
355
+ :type priors: Union[dict, None]
356
+ :param fiducial_parameters: The starting guesses for model parameters to
357
+ use in the optimization for maximum likelihood estimation. Default to None if not provided.
358
+ :type fiducial_parameters: Union[dict, None]
359
+ """
360
+ self.sigma_i = sigma_i
361
+ # These lines of code infer the parameters from the provided function
362
+ super().__init__(x=x, y=y, sigma=sigma_i, function=function, kwargs=kwargs, priors=priors,
363
+ fiducial_parameters=fiducial_parameters)
364
+
365
+ @property
366
+ def full_sigma(self) -> Union[float, np.ndarray]:
367
+ """
368
+ :return: The standard deviation of the full noise
369
+ :rtype: Union[float, np.ndarray]
370
+ """
371
+ model_y = self.function(self.x, **self.parameters, **self.kwargs)
372
+ return np.sqrt(self.sigma_i**2.*model_y**2)
373
+
374
+ def noise_log_likelihood(self) -> float:
375
+ """
376
+ :return: The noise log-likelihood, i.e. the log-likelihood assuming the signal is just noise.
377
+ :rtype: float
378
+ """
379
+ if self._noise_log_likelihood is None:
380
+ self._noise_log_likelihood = self._gaussian_log_likelihood(res=self.y, sigma=self.sigma_i)
381
+ return self._noise_log_likelihood
382
+
383
+ def log_likelihood(self) -> float:
384
+ """
385
+ :return: The log-likelihood.
386
+ :rtype: float
387
+ """
388
+ return np.nan_to_num(self._gaussian_log_likelihood(res=self.residual, sigma=self.full_sigma))
389
+
234
390
  class GaussianLikelihoodWithSystematicNoise(GaussianLikelihood):
235
391
  def __init__(
236
392
  self, x: np.ndarray, y: np.ndarray, sigma_i: Union[float, None, np.ndarray],
237
- function: callable, kwargs: dict = None) -> None:
393
+ function: callable, kwargs: dict = None, priors=None, fiducial_parameters=None) -> None:
238
394
  """
239
- A general Gaussian likelihood - the parameters are inferred from the
240
- arguments of function
395
+ A Gaussian likelihood with a systematic noise term that is proportional to the model + some additive noise.
396
+ The parameters are inferred from the arguments of function
241
397
 
398
+ :param x: The x values.
242
399
  :type x: np.ndarray
243
400
  :param y: The y values.
244
401
  :type y: np.ndarray
245
402
  :param sigma_i: The standard deviation of the noise. This is part of the full noise.
246
- The sigma used in the likelihood is sigma = sqrt(sigma_i^2 + sigma^2)
403
+ The sigma used in the likelihood is sigma = sqrt(sigma_i^2 + model_y**2*sigma^2)
247
404
  :type sigma_i: Union[float, None, np.ndarray]
248
405
  :param function:
249
406
  The python function to fit to the data. Note, this must take the
@@ -253,10 +410,17 @@ class GaussianLikelihoodWithSystematicNoise(GaussianLikelihood):
253
410
  :type function: callable
254
411
  :param kwargs: Any additional keywords for 'function'.
255
412
  :type kwargs: dict
413
+ :param priors: The priors for the parameters. Default to None if not provided.
414
+ Only necessary if using maximum likelihood estimation functionality.
415
+ :type priors: Union[dict, None]
416
+ :param fiducial_parameters: The starting guesses for model parameters to
417
+ use in the optimization for maximum likelihood estimation. Default to None if not provided.
418
+ :type fiducial_parameters: Union[dict, None]
256
419
  """
257
420
  self.sigma_i = sigma_i
258
421
  # These lines of code infer the parameters from the provided function
259
- super().__init__(x=x, y=y, sigma=sigma_i, function=function, kwargs=kwargs)
422
+ super().__init__(x=x, y=y, sigma=sigma_i, function=function, kwargs=kwargs, priors=priors,
423
+ fiducial_parameters=fiducial_parameters)
260
424
 
261
425
  @property
262
426
  def full_sigma(self) -> Union[float, np.ndarray]:
@@ -286,10 +450,11 @@ class GaussianLikelihoodWithSystematicNoise(GaussianLikelihood):
286
450
  class GaussianLikelihoodQuadratureNoiseNonDetections(GaussianLikelihoodQuadratureNoise):
287
451
  def __init__(
288
452
  self, x: np.ndarray, y: np.ndarray, sigma_i: Union[float, np.ndarray], function: callable,
289
- kwargs: dict = None, upperlimit_kwargs: dict = None) -> None:
453
+ kwargs: dict = None, upperlimit_kwargs: dict = None, priors=None, fiducial_parameters=None) -> None:
290
454
  """A general Gaussian likelihood - the parameters are inferred from the
291
455
  arguments of function. Takes into account non-detections with a Uniform likelihood for those points
292
456
 
457
+ :param x: The x values.
293
458
  :type x: np.ndarray
294
459
  :param y: The y values.
295
460
  :type y: np.ndarray
@@ -304,8 +469,15 @@ class GaussianLikelihoodQuadratureNoiseNonDetections(GaussianLikelihoodQuadratur
304
469
  :type function: callable
305
470
  :param kwargs: Any additional keywords for 'function'.
306
471
  :type kwargs: dict
307
- """
308
- super().__init__(x=x, y=y, sigma_i=sigma_i, function=function, kwargs=kwargs)
472
+ :param priors: The priors for the parameters. Default to None if not provided.
473
+ Only necessary if using maximum likelihood estimation functionality.
474
+ :type priors: Union[dict, None]
475
+ :param fiducial_parameters: The starting guesses for model parameters to
476
+ use in the optimization for maximum likelihood estimation. Default to None if not provided.
477
+ :type fiducial_parameters: Union[dict, None]
478
+ """
479
+ super().__init__(x=x, y=y, sigma_i=sigma_i, function=function, kwargs=kwargs, priors=priors,
480
+ fiducial_parameters=fiducial_parameters)
309
481
  self.upperlimit_kwargs = upperlimit_kwargs
310
482
 
311
483
  @property
@@ -345,7 +517,7 @@ class GRBGaussianLikelihood(GaussianLikelihood):
345
517
 
346
518
  def __init__(
347
519
  self, x: np.ndarray, y: np.ndarray, sigma: Union[float, np.ndarray],
348
- function: callable, kwargs: dict = None) -> None:
520
+ function: callable, kwargs: dict = None, priors=None, fiducial_parameters=None) -> None:
349
521
  """A general Gaussian likelihood - the parameters are inferred from the
350
522
  arguments of function.
351
523
 
@@ -363,14 +535,21 @@ class GRBGaussianLikelihood(GaussianLikelihood):
363
535
  :type function: callable
364
536
  :param kwargs: Any additional keywords for 'function'.
365
537
  :type kwargs: dict
538
+ :param priors: The priors for the parameters. Default to None if not provided.
539
+ Only necessary if using maximum likelihood estimation functionality.
540
+ :type priors: Union[dict, None]
541
+ :param fiducial_parameters: The starting guesses for model parameters to
542
+ use in the optimization for maximum likelihood estimation. Default to None if not provided.
543
+ :type fiducial_parameters: Union[dict, None]
366
544
  """
367
- super().__init__(x=x, y=y, sigma=sigma, function=function, kwargs=kwargs)
545
+ super().__init__(x=x, y=y, sigma=sigma, function=function, kwargs=kwargs, priors=priors,
546
+ fiducial_parameters=fiducial_parameters)
368
547
 
369
548
 
370
549
  class PoissonLikelihood(_RedbackLikelihood):
371
550
  def __init__(
372
551
  self, time: np.ndarray, counts: np.ndarray, function: callable, integrated_rate_function: bool = True,
373
- dt: Union[float, np.ndarray] = None, kwargs: dict = None) -> None:
552
+ dt: Union[float, np.ndarray] = None, kwargs: dict = None, priors=None, fiducial_parameters=None) -> None:
374
553
  """
375
554
  :param time: The time values.
376
555
  :type time: np.ndarray
@@ -389,8 +568,15 @@ class PoissonLikelihood(_RedbackLikelihood):
389
568
  :type dt: Union[float, None, np.ndarray]
390
569
  :param kwargs: Any additional keywords for 'function'.
391
570
  :type kwargs: dict
392
- """
393
- super(PoissonLikelihood, self).__init__(x=time, y=counts, function=function, kwargs=kwargs)
571
+ :param priors: The priors for the parameters. Default to None if not provided.
572
+ Only necessary if using maximum likelihood estimation functionality.
573
+ :type priors: Union[dict, None]
574
+ :param fiducial_parameters: The starting guesses for model parameters to
575
+ use in the optimization for maximum likelihood estimation. Default to None if not provided.
576
+ :type fiducial_parameters: Union[dict, None]
577
+ """
578
+ super(PoissonLikelihood, self).__init__(x=time, y=counts, function=function, kwargs=kwargs, priors=priors,
579
+ fiducial_parameters=fiducial_parameters)
394
580
  self.integrated_rate_function = integrated_rate_function
395
581
  self.dt = dt
396
582
  self.parameters['background_rate'] = 0