• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nikhil-sarin / redback / 14430354752

13 Apr 2025 02:23PM UTC coverage: 86.635% (+6.0%) from 80.663%
14430354752

Pull #266

github

web-flow
Merge 8147dba2c into e087188ab
Pull Request #266: A big overhaul

1621 of 1828 new or added lines in 14 files covered. (88.68%)

4 existing lines in 2 files now uncovered.

12673 of 14628 relevant lines covered (86.64%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.41
/redback/simulate_transients.py
1
import numpy as np
1✔
2
from sncosmo import TimeSeriesSource, Model, get_bandpass
1✔
3
import redback
1✔
4
import pandas as pd
1✔
5
from redback.utils import logger, calc_flux_density_error_from_monochromatic_magnitude, calc_flux_density_from_ABmag
1✔
6
from itertools import repeat
1✔
7
import astropy.units as uu
1✔
8
from scipy.spatial import KDTree
1✔
9
import os
1✔
10
import bilby
1✔
11
import random
1✔
12
from astropy.cosmology import Planck18 as cosmo
1✔
13
datadir = os.path.join(os.path.dirname(redback.__file__), 'tables')
1✔
14

15
class SimulateGenericTransient(object):
1✔
16
    def __init__(self, model, parameters, times, model_kwargs, data_points,
1✔
17
                 seed=1234, multiwavelength_transient=False, noise_term=0.2, noise_type='gaussianmodel', extra_scatter=0.0):
18
        """
19
        A generic interface to simulating transients
20

21
        :param model: String corresponding to redback model or a python function that can evaluate an SED.
22
        :param parameters: Dictionary of parameters describing a single transient
23
        :param times: Time values that the model is evaluated from
24
        :param model_kwargs: Additional keyword arguments, must include all the keyword arguments required by the model.
25
                Refer to the model documentation for details
26
        :param data_points: Number of data points to randomly sample.
27
                This will randomly sample data_points in time and in bands or frequency.
28
        :param seed: random seed for reproducibility
29
        :param multiwavelength_transient: Boolean.
30
                If True, the model is assumed to be a transient which has multiple bands/frequency
31
                and the data points are sampled in bands/frequency as well,
32
                rather than just corresponding to one wavelength/filter.
33
                This also allows the same time value to be sampled multiple times.
34
        :param noise_type: String. Type of noise to add to the model.
35
            Default is 'gaussianmodel' where sigma is noise_term * model.
36
            Another option is 'gaussian' i.e., a simple Gaussian noise with sigma = noise_term.
37
        :param noise_term: Float. Factor which is multiplied by the model flux/magnitude to give the sigma
38
            or is sigma itself for 'gaussian' noise. Or the SNR for 'SNRbased' noise.
39
        :param extra_scatter: Float. Sigma of normal added to output for additional scatter.
40
        """
41
        if model in redback.model_library.all_models_dict:
1✔
42
            self.model = redback.model_library.all_models_dict[model]
×
43
        else:
44
            self.model = model 
1✔
45
        self.parameters = parameters
1✔
46
        self.all_times = times
1✔
47
        self.model_kwargs = model_kwargs
1✔
48
        self.multiwavelength_transient = multiwavelength_transient
1✔
49
        self.data_points = data_points
1✔
50
        self.seed = seed
1✔
51
        self.random_state = np.random.RandomState(seed=self.seed)
1✔
52
        self.noise_term = noise_term
1✔
53
        random.seed(self.seed)
1✔
54

55
        self.all_bands = self.model_kwargs.get('bands', None)
1✔
56
        self.all_frequency = self.model_kwargs.get('frequency', None)
1✔
57
        if self.all_bands is None and self.all_frequency is None:
1✔
58
            raise ValueError('Must supply either bands or frequency to sample data points for an optical transient')
×
59
        else:
60
            if multiwavelength_transient:
1✔
61
                if self.all_bands is not None and self.all_frequency is None:
1✔
62
                    self.subset_bands = np.array(random.choices(self.all_bands, k=self.data_points))
1✔
63
                if self.all_bands is None and self.all_frequency is not None:
1✔
64
                    self.subset_frequency = np.array(random.choices(self.all_frequency, k=self.data_points))
×
65
                self.replacement = True
1✔
66
                # allow times to be chosen repeatedly
67
            else:
68
                if self.all_bands is not None and self.all_frequency is None:
1✔
69
                    self.subset_bands = self.data_points * [self.all_bands]
1✔
70
                if self.all_bands is None and self.all_frequency is not None:
1✔
71
                    self.subset_frequency = np.ones(self.data_points) * self.all_frequency
×
72
                # allow times to be chosen only once.
73
                self.replacement = False
1✔
74
        self.subset_times = np.sort(np.random.choice(self.all_times, size=self.data_points, replace=self.replacement))
1✔
75

76
        injection_kwargs = self.parameters.copy()
1✔
77
        if 'bands' in model_kwargs.keys():
1✔
78
            injection_kwargs['bands'] = self.subset_bands
1✔
79
            injection_kwargs['output_format'] = model_kwargs['output_format']
1✔
80
        if 'frequency' in model_kwargs.keys():
1✔
81
            injection_kwargs['frequency'] = self.subset_frequency
×
82
            injection_kwargs['output_format'] = 'flux_density'
×
83

84
        true_output = self.model(self.subset_times, **injection_kwargs)
1✔
85
        data = pd.DataFrame()
1✔
86
        data['time'] = self.subset_times
1✔
87
        if 'bands' in model_kwargs.keys():
1✔
88
            data['band'] = self.subset_bands
1✔
89
        if 'frequency' in model_kwargs.keys():
1✔
90
            data['frequency'] = self.subset_frequency
×
91
        data['true_output'] = true_output
1✔
92

93
        if noise_type == 'gaussianmodel':
1✔
94
            noise = np.random.normal(0, self.noise_term * true_output, len(true_output))
1✔
95
            output = true_output + noise
1✔
96
            output_error = self.noise_term * true_output
1✔
97
        elif noise_type == 'gaussian':
1✔
98
            noise = np.random.normal(0, self.noise_term, len(true_output))
1✔
99
            output = true_output + noise
1✔
100
            output_error = self.noise_term
1✔
101
        elif noise_type == 'SNRbased':
1✔
102
            sigma = np.sqrt(true_output + np.min(true_output)/self.noise_term)
×
103
            output_error = sigma
×
104
            output = true_output + np.random.normal(0, sigma, len(true_output))
×
105
        else:
106
            logger.warning(f"noise_type {noise_type} not implemented.")
1✔
107
            raise ValueError('noise_type must be either gaussianmodel, gaussian, or SNRBased')
1✔
108

109
        if extra_scatter > 0:
1✔
110
            extra_noise = np.random.normal(0, extra_scatter, len(true_output))
1✔
111
            output = output + extra_noise
1✔
112
            output_error = np.sqrt(output_error**2 + extra_noise**2)
1✔
113

114
        data['output'] = output
1✔
115
        data['output_error'] = output_error
1✔
116
        self.data = data
1✔
117

118
    def save_transient(self, name):
1✔
119
        """
120
        Save the transient observations to a csv file.
121
        This will save the full observational dataframe including non-detections etc.
122
        This will save the data to a folder called 'simulated'
123
        with the name of the transient and a csv file of the injection parameters
124

125
        :param name: name to save transient.
126
        """
127
        bilby.utils.check_directory_exists_and_if_not_mkdir('simulated')
1✔
128
        path = 'simulated/' + name + '.csv'
1✔
129
        injection_path = 'simulated/' + name + '_injection_parameters.csv'
1✔
130
        self.data.to_csv(path, index=False)
1✔
131
        self.parameters=pd.DataFrame.from_dict([self.parameters])
1✔
132
        self.parameters.to_csv(injection_path, index=False)
1✔
133

134
class SimulateOpticalTransient(object):
1✔
135
    def __init__(self, model, parameters, pointings_database=None,
1✔
136
                 survey='Rubin_10yr_baseline',sncosmo_kwargs=None, obs_buffer=5.0,
137
                 survey_fov_sqdeg=9.6,snr_threshold=5, end_transient_time=1000, add_source_noise=False,
138
                 population=False, model_kwargs=None, **kwargs):
139
        """
140
        Simulate an optical transient or transient population for an optical Survey like Rubin, ZTF, Roman etc
141

142
        :param model: String corresponding to redback model or a python function that can evaluate an SED.
143
        :param parameters: Dictionary of parameters describing a single transient or a transient population.
144
                This can either include RA and DEC or it is randomly drawn from the pointing database.
145
                Must include t0_mjd_transient or t0.
146
        :param pointings_database: A pandas DataFrame containing the pointings of the survey.
147
        :param survey: String corresponding to the survey name. This is used to look up the pointings database.
148
                Set to LSST 10 year baseline 3.0 by default. If None, the user must supply a pointings_database.
149
                Implemented surveys currently include a Rubin 10 year baseline 3.0 as 'Rubin_10yr_baseline, and ZTF as 'ztf'.
150
        :param sncosmo_kwargs: Any kwargs to be passed to SNcosmo.
151
                SNcosmo is used to evaluate the bandpass magnitudes in different bands.
152
        :param obs_buffer: A observation buffer in days to add to the start of the transient
153
                to allow for non-detections. Default is 5 days
154
        :param survey_fov_sqdeg: Survey field of view. Default is 9.6 sqdeg for Rubin.
155
                36" for ZTF as a circular approximation to the square FOV of ZTF.
156
        :param snr_threshold: SNR threshold for detection. Default is 5.
157
        :param end_transient_time: End time of the transient in days. Default is 1000 days.
158
                Note that SNCosmo will extrapolate past when the transient model evaluates the SED so these should really be the same.
159
        :param add_source_noise: Boolean. If True, add an extra noise in quadrature to the limiting mag noise.
160
                The factor is a multiple of the model flux i.e. noise = (skynoise**2 + (model_flux*source_noise)**2)**0.5
161
        :param population: Boolean. If True, the parameters are assumed to be for a population of transients.
162
        :param model_kwargs: Dictionary of kwargs to be passed to the model.
163
        :param kwargs: Dictionary of additional kwargs
164
        :param source_noise: Float. Factor to multiply the model flux by to add an extra noise
165
                in quadrature to the limiting mag noise. Default value is 0.02, disabled by default.
166
        """
167

168
        self.survey_fov_sqdeg = survey_fov_sqdeg
1✔
169
        self.snr_threshold = snr_threshold
1✔
170
        self.population = population
1✔
171
        self.source_noise_factor = kwargs.get('source_noise', 0.02)
1✔
172
        if population:
1✔
173
            self.parameters = pd.DataFrame(parameters)
×
174
        else:
175
            self.parameters = pd.DataFrame(parameters, index=[0])
1✔
176
        self.sncosmo_kwargs = sncosmo_kwargs
1✔
177
        self.obs_buffer = obs_buffer
1✔
178
        self.end_transient_time = self.t0_transient + end_transient_time
1✔
179
        self.add_source_noise = add_source_noise
1✔
180

181
        if isinstance(model, str):
1✔
182
            self.model = redback.model_library.all_models_dict[model]
1✔
183
            model_kwargs['output_format'] = 'sncosmo_source'
1✔
184
            _time_array = np.linspace(0.1, 3000.0, 10)
1✔
185
            if self.population:
1✔
186
                self.all_sncosmo_models = []
×
187
                for x in range(len(self.parameters)):
×
188
                    sncosmomodel = self.model(_time_array, **self.parameters.iloc[x], **model_kwargs)
×
189
                    self.all_sncosmo_models.append(sncosmomodel)
×
190
            else:
191
                self.sncosmo_model = self.model(_time_array, **parameters, **model_kwargs)
1✔
192
        elif callable(model):
×
193
            self.model = model
×
194
            logger.info('Using custom model. Making a SNCosmo wrapper for this model')
×
195
            self.sncosmo_model = self._make_sncosmo_wrapper_for_user_model()
×
196
        else:
197
            raise ValueError("The user needs to specify model as either a string or function.")
×
198

199
        if survey is not None:
1✔
200
            self.pointings_database_name = self._survey_to_table_name_lookup(survey)
1✔
201
            self.pointings_database = pd.read_csv(datadir + "/" + self.pointings_database_name, compression='gzip')
1✔
202
            logger.info(f"Using {self.pointings_database_name} as the pointing database corresponding to {survey}.")
1✔
203
        else:
204
            self.pointings_database = pointings_database
×
205
            self.pointings_database_name = 'user_defined'
×
206
            if isinstance(self.pointings_database, pd.DataFrame):
×
207
                logger.info(f"Using the supplied as the pointing database.")
×
208
            else:
209
                raise ValueError("The user needs to specify survey as either a string or a "
×
210
                                 "pointings_database pandas DataFrame.")
211

212
        self.parameters['ra'] = self.RA
1✔
213
        self.parameters['dec'] = self.DEC
1✔
214

215
        if population:
1✔
216
            self.list_of_observations = self._make_observations_for_population()
×
217
            self.list_of_inference_observations = self._make_inference_dataframe()
×
218
        else:
219
            self.observations = self._make_observations()
1✔
220
            self.inference_observations = self._make_inference_dataframe()
1✔
221
            self.inference_observations = self._make_inference_dataframe()
1✔
222

223
    @classmethod
1✔
224
    def simulate_transient(cls, model, parameters, pointings_database=None,
1✔
225
                 survey='Rubin_10yr_baseline',sncosmo_kwargs=None, obs_buffer=5.0, survey_fov_sqdeg=9.6,
226
                 snr_threshold=5, end_transient_time=1000, add_source_noise=False, model_kwargs=None, **kwargs):
227
        """
228
        Constructor method to build simulated transient object for a single transient.
229

230
        :param model: String corresponding to redback model or a python function that can evaluate an SED.
231
        :param parameters: Dictionary of parameters describing a single transient or a transient population.
232
            This can either include RA and DEC or it is randomly drawn from the pointing database.
233
            Must include t0_mjd_transient or t0.
234
        :param pointings_database: A pandas DataFrame containing the pointings of the survey.
235
        :param survey: String corresponding to the survey name. This is used to look up the pointings database.
236
            Set to LSST 10 year baseline 3.0 by default.
237
        :param sncosmo_kwargs: Any kwargs to be passed to SNcosmo.
238
            SNcosmo is used to evaluate the bandpass magnitudes in different bands.
239
        :param obs_buffer: A observation buffer in days to add to the start of the transient
240
            to allow for non-detections. Default is 5 days
241
        :param survey_fov_sqdeg: Survey field of view. Default is 9.6 sqdeg for Rubin.
242
        :param snr_threshold: SNR threshold for detection. Default is 5.
243
        :param end_transient_time: End time of the transient in days. Default is 1000 days.
244
            Note that SNCosmo will extrapolate past when the transient model evaluates the SED so these should really be the same.
245
        :param add_source_noise: Boolean. If True, add an extra noise in quadrature to the limiting mag noise.
246
            The factor is a multiple of the model flux i.e. noise = (skynoise**2 + (model_flux*source_noise)**2)**0.5
247
        :param population: Boolean. If True, the parameters are assumed to be for a population of transients.
248
        :param model_kwargs: Dictionary of kwargs to be passed to the model.
249
        :param kwargs: Dictionary of additional kwargs
250
        :param source_noise: Float. Factor to multiply the model flux by to add an extra noise
251
            in quadrature to the limiting mag noise. Default value is 0.02, disabled by default.
252
        """
253
        return cls(model=model, parameters=parameters, pointings_database=pointings_database, survey=survey,
×
254
                   sncosmo_kwargs=sncosmo_kwargs, obs_buffer=obs_buffer,
255
                   survey_fov_sqdeg=survey_fov_sqdeg, snr_threshold=snr_threshold,
256
                   end_transient_time=end_transient_time, add_source_noise=add_source_noise,
257
                   population=False, model_kwargs=model_kwargs, **kwargs)
258

259
    @classmethod
1✔
260
    def simulate_transient_in_rubin(cls, model, parameters, pointings_database=None,
1✔
261
                 survey='Rubin_10yr_baseline',sncosmo_kwargs=None, obs_buffer=5.0,
262
                snr_threshold=5, end_transient_time=1000, add_source_noise=False, model_kwargs=None, **kwargs):
263
        """
264
        Constructor method to build simulated transient object for a single transient with Rubin.
265

266
        :param model: String corresponding to redback model or a python function that can evaluate an SED.
267
        :param parameters: Dictionary of parameters describing a single transient or a transient population.
268
            This can either include RA and DEC or it is randomly drawn from the pointing database.
269
            Must include t0_mjd_transient or t0.
270
        :param pointings_database: A pandas DataFrame containing the pointings of the survey.
271
        :param survey: String corresponding to the survey name. This is used to look up the pointings database.
272
            Set to LSST 10 year baseline 3.0 by default.
273
        :param sncosmo_kwargs: Any kwargs to be passed to SNcosmo.
274
            SNcosmo is used to evaluate the bandpass magnitudes in different bands.
275
        :param obs_buffer: A observation buffer in days to add to the start of the transient
276
            to allow for non-detections. Default is 5 days
277
        :param snr_threshold: SNR threshold for detection. Default is 5.
278
        :param end_transient_time: End time of the transient in days. Default is 1000 days.
279
            Note that SNCosmo will extrapolate past when the transient model evaluates the SED so these should really be the same.
280
        :param add_source_noise: Boolean. If True, add an extra noise in quadrature to the limiting mag noise.
281
            The factor is a multiple of the model flux i.e. noise = (skynoise**2 + (model_flux*source_noise)**2)**0.5
282
        :param model_kwargs: Dictionary of kwargs to be passed to the model.
283
        :param kwargs: Dictionary of additional kwargs
284
        :param source_noise: Float. Factor to multiply the model flux by to add an extra noise
285
            in quadrature to the limiting mag noise. Default value is 0.02, disabled by default.
286
        """
287
        return cls(model=model, parameters=parameters, pointings_database=pointings_database, survey=survey,
×
288
                   sncosmo_kwargs=sncosmo_kwargs, obs_buffer=obs_buffer,
289
                   survey_fov_sqdeg=9.6, snr_threshold=snr_threshold,
290
                   end_transient_time=end_transient_time, add_source_noise=add_source_noise,
291
                   population=False, model_kwargs=model_kwargs, **kwargs)
292

293
    @classmethod
1✔
294
    def simulate_transient_in_ztf(cls, model, parameters, pointings_database=None,
1✔
295
                 survey='ztf',sncosmo_kwargs=None, obs_buffer=5.0,
296
                  snr_threshold=5, end_transient_time=1000, add_source_noise=False, model_kwargs=None, **kwargs):
297
        """
298
        Constructor method to build simulated transient object for a single transient with ZTF.
299

300
        :param model: String corresponding to redback model or a python function that can evaluate an SED.
301
        :param parameters: Dictionary of parameters describing a single transient or a transient population.
302
            This can either include RA and DEC or it is randomly drawn from the pointing database.
303
            Must include t0_mjd_transient or t0.
304
        :param pointings_database: A pandas DataFrame containing the pointings of the survey.
305
        :param survey: String corresponding to the survey name. This is used to look up the pointings database.
306
        :param sncosmo_kwargs: Any kwargs to be passed to SNcosmo.
307
            SNcosmo is used to evaluate the bandpass magnitudes in different bands.
308
        :param obs_buffer: A observation buffer in days to add to the start of the transient
309
            to allow for non-detections. Default is 5 days
310
        :param snr_threshold: SNR threshold for detection. Default is 5.
311
        :param end_transient_time: End time of the transient in days. Default is 1000 days.
312
            Note that SNCosmo will extrapolate past when the transient model evaluates the SED so these should really be the same.
313
        :param add_source_noise: Boolean. If True, add an extra noise in quadrature to the limiting mag noise.
314
            The factor is a multiple of the model flux i.e. noise = (skynoise**2 + (model_flux*source_noise)**2)**0.5
315
        :param model_kwargs: Dictionary of kwargs to be passed to the model.
316
        :param kwargs: Dictionary of additional kwargs
317
        :param source_noise: Float. Factor to multiply the model flux by to add an extra noise
318
            in quadrature to the limiting mag noise. Default value is 0.02, disabled by default.
319
        """
320
        return cls(model=model, parameters=parameters, pointings_database=pointings_database, survey=survey,
×
321
                   sncosmo_kwargs=sncosmo_kwargs, obs_buffer=obs_buffer,
322
                   survey_fov_sqdeg=36., snr_threshold=snr_threshold,
323
                   end_transient_time=end_transient_time,add_source_noise=add_source_noise,
324
                   population=False, model_kwargs=model_kwargs, **kwargs)
325

326
    @classmethod
1✔
327
    def simulate_transient_population(cls, model, parameters, pointings_database=None,
1✔
328
                 survey='Rubin_10yr_baseline',sncosmo_kwargs=None,obs_buffer=5.0, survey_fov_sqdeg=9.6,
329
                 snr_threshold=5, end_transient_time=1000, add_source_noise=False, model_kwargs=None, **kwargs):
330
        """
331
        Constructor method to build simulated transient object for a single transient.
332

333
        :param model: String corresponding to redback model or a python function that can evaluate an SED.
334
        :param parameters: Dictionary of parameters describing a single transient or a transient population.
335
            This can either include RA and DEC or it is randomly drawn from the pointing database.
336
            Must include t0_mjd_transient or t0.
337
        :param pointings_database: A pandas DataFrame containing the pointings of the survey.
338
        :param survey: String corresponding to the survey name. This is used to look up the pointings database.
339
            Set to LSST 10 year baseline 3.0 by default.
340
        :param sncosmo_kwargs: Any kwargs to be passed to SNcosmo.
341
            SNcosmo is used to evaluate the bandpass magnitudes in different bands.
342
        :param obs_buffer: A observation buffer in days to add to the start of the transient
343
            to allow for non-detections. Default is 5 days
344
        :param survey_fov_sqdeg: Survey field of view. Default is 9.6 sqdeg for Rubin.
345
        :param snr_threshold: SNR threshold for detection. Default is 5.
346
        :param end_transient_time: End time of the transient in days. Default is 1000 days.
347
            Note that SNCosmo will extrapolate past when the transient model evaluates the SED so these should really be the same.
348
        :param add_source_noise: Boolean. If True, add an extra noise in quadrature to the limiting mag noise.
349
            The factor is a multiple of the model flux i.e. noise = (skynoise**2 + (model_flux*source_noise)**2)**0.5
350
        :param model_kwargs: Dictionary of kwargs to be passed to the model.
351
        :param kwargs: Dictionary of additional kwargs
352
        :param source_noise: Float. Factor to multiply the model flux by to add an extra noise
353
            in quadrature to the limiting mag noise. Default value is 0.02, disabled by default.
354
        """
355
        return cls(model=model, parameters=parameters, pointings_database=pointings_database, survey=survey,
×
356
                   sncosmo_kwargs=sncosmo_kwargs, obs_buffer=obs_buffer,
357
                   survey_fov_sqdeg=survey_fov_sqdeg, snr_threshold=snr_threshold,
358
                   end_transient_time=end_transient_time, add_source_noise=add_source_noise,
359
                   population=True, model_kwargs=model_kwargs, **kwargs)
360

361
    @classmethod
1✔
362
    def simulate_transient_population_in_rubin(cls, model, parameters, pointings_database=None,
1✔
363
                 survey='Rubin_10yr_baseline',sncosmo_kwargs=None, obs_buffer=5.0,
364
                 snr_threshold=5, end_transient_time=1000, add_source_noise=False, model_kwargs=None, **kwargs):
365
        """
366
        Constructor method to build simulated transient object for a single transient.
367

368
        :param model: String corresponding to redback model or a python function that can evaluate an SED.
369
        :param parameters: Dictionary of parameters describing a single transient or a transient population.
370
            This can either include RA and DEC or it is randomly drawn from the pointing database.
371
            Must include t0_mjd_transient or t0.
372
        :param pointings_database: A pandas DataFrame containing the pointings of the survey.
373
        :param survey: String corresponding to the survey name. This is used to look up the pointings database.
374
            Set to LSST 10 year baseline 3.0 by default.
375
        :param sncosmo_kwargs: Any kwargs to be passed to SNcosmo.
376
            SNcosmo is used to evaluate the bandpass magnitudes in different bands.
377
        :param obs_buffer: A observation buffer in days to add to the start of the transient
378
            to allow for non-detections. Default is 5 days
379
        :param snr_threshold: SNR threshold for detection. Default is 5.
380
        :param end_transient_time: End time of the transient in days. Default is 1000 days.
381
        :param add_source_noise: Boolean. If True, add an extra noise in quadrature to the limiting mag noise.
382
            The factor is a multiple of the model flux i.e. noise = (skynoise**2 + (model_flux*source_noise)**2)**0.5
383
        :param model_kwargs: Dictionary of kwargs to be passed to the model.
384
        :param kwargs: Dictionary of additional kwargs
385
        :param source_noise: Float. Factor to multiply the model flux by to add an extra noise
386
            in quadrature to the limiting mag noise. Default value is 0.02, disabled by default.
387
        """
388
        return cls(model=model, parameters=parameters, pointings_database=pointings_database, survey=survey,
×
389
                   sncosmo_kwargs=sncosmo_kwargs, obs_buffer=obs_buffer,
390
                   survey_fov_sqdeg=9.6, snr_threshold=snr_threshold,
391
                   end_transient_time=end_transient_time,add_source_noise=add_source_noise,
392
                   population=True, model_kwargs=model_kwargs, **kwargs)
393

394
    @classmethod
1✔
395
    def simulate_transient_population_in_ztf(cls, model, parameters, pointings_database=None,
1✔
396
                 survey='ztf',sncosmo_kwargs=None, obs_buffer=5.0,
397
                 snr_threshold=5, end_transient_time=1000, add_source_noise=False, model_kwargs=None, **kwargs):
398
        """
399
        Constructor method to build simulated transient object for a single transient.
400

401
        :param model: String corresponding to redback model or a python function that can evaluate an SED.
402
        :param parameters: Dictionary of parameters describing a single transient or a transient population.
403
            This can either include RA and DEC or it is randomly drawn from the pointing database.
404
            Must include t0_mjd_transient or t0.
405
        :param pointings_database: A pandas DataFrame containing the pointings of the survey.
406
        :param survey: String corresponding to the survey name. This is used to look up the pointings database.
407
        :param sncosmo_kwargs: Any kwargs to be passed to SNcosmo.
408
            SNcosmo is used to evaluate the bandpass magnitudes in different bands.
409
        :param obs_buffer: A observation buffer in days to add to the start of the transient
410
            to allow for non-detections. Default is 5 days
411
        :param snr_threshold: SNR threshold for detection. Default is 5.
412
        :param end_transient_time: End time of the transient in days. Default is 1000 days.
413
            Note that SNCosmo will extrapolate past when the transient model evaluates the SED so these should really be the same.
414
        :param add_source_noise: Boolean. If True, add an extra noise in quadrature to the limiting mag noise.
415
            The factor is a multiple of the model flux i.e. noise = (skynoise**2 + (model_flux*source_noise)**2)**0.5
416
        :param model_kwargs: Dictionary of kwargs to be passed to the model.
417
        :param kwargs: Dictionary of additional kwargs
418
        :param source_noise: Float. Factor to multiply the model flux by to add an extra noise
419
            in quadrature to the limiting mag noise. Default value is 0.02, disabled by default.
420
        """
421
        return cls(model=model, parameters=parameters, pointings_database=pointings_database, survey=survey,
×
422
                   sncosmo_kwargs=sncosmo_kwargs, obs_buffer=obs_buffer,
423
                   survey_fov_sqdeg=36., snr_threshold=snr_threshold,
424
                   end_transient_time=end_transient_time, add_source_noise=add_source_noise,
425
                   population=True, model_kwargs=model_kwargs, **kwargs)
426

427
    def _make_inference_dataframe(self):
1✔
428
        """
429
        Make a dataframe that can be used for inference.
430
        This removes all the non-detections from the observations dataframe.
431

432
        :return:
433
        """
434
        if self.population:
1✔
435
            all_data = self.list_of_observations
×
436
            events = len(self.parameters)
×
437
            dfs = []
×
438
            for x in range(events):
×
439
                df = all_data[x]
×
440
                df = df[df.detected != 0]
×
441
                dfs.append(df)
×
442
            return dfs
×
443
        else:
444
            df = self.observations
1✔
445
            df = df[df.detected != 0]
1✔
446
            return df
1✔
447

448
    @property
1✔
449
    def survey_radius(self):
1✔
450
        """
451
        Convert the circular field of view to a radius in radians.
452
        :return: survey_radius in radians
453
        """
454
        survey_fov_sqrad = self.survey_fov_sqdeg*(np.pi/180.0)**2
1✔
455
        survey_radius = np.sqrt(survey_fov_sqrad/np.pi)
1✔
456
        # survey_radius = np.sqrt(self.survey_fov_sqdeg*((np.pi/180.0)**2.0)/np.pi)
457
        return survey_radius
1✔
458

459
    @property
1✔
460
    def t0_transient(self):
1✔
461
        """
462
        :return: The start time of the transient in MJD
463
        """
464
        if 't0' in self.parameters:
1✔
465
            return self.parameters['t0']
1✔
466
        else:
467
            return self.parameters['t0_mjd_transient']
×
468

469
    @property
1✔
470
    def RA(self):
1✔
471
        """
472
        :return: The RA of the transient in radians. Draw randomly from the pointings database if not supplied.
473
        """
474
        if 'ra' in self.parameters:
1✔
475
            RA = self.parameters['ra'].values
×
476
        else:
477
            RA = self.pointings_database['_ra'].sample(len(self.parameters)).values
1✔
478
        return RA
1✔
479

480
    @property
1✔
481
    def DEC(self):
1✔
482
        """
483
        :return: The DEC of the transient in radians. Draw randomly from the pointings database if not supplied.
484
        """
485
        if 'dec' in self.parameters:
1✔
486
            dec = self.parameters['dec'].values
×
487
        else:
488
            dec = self.pointings_database['_dec'].sample(len(self.parameters)).values
1✔
489
        return dec
1✔
490

491
    @property
1✔
492
    def min_dec(self):
1✔
493
        """
494
        :return: Minimum dec of the survey in radians
495
        """
496
        df = self.pointings_database
×
497
        return np.min(df['_dec'])
×
498

499
    @property
1✔
500
    def max_dec(self):
1✔
501
        """
502
        :return: Maximum dec of the survey in radians
503
        """
504
        df = self.pointings_database
×
505
        return np.max(df['_dec'])
×
506

507
    @property
1✔
508
    def start_mjd(self):
1✔
509
        """
510
        :return: Start of the survey in MJD
511
        """
512
        df = self.pointings_database
×
513
        return np.min(df['expMJD'])
×
514

515
    @property
1✔
516
    def end_mjd(self):
1✔
517
        """
518
        :return: End of the survey in MJD
519
        """
520
        df = self.pointings_database
×
521
        return np.max(df['expMJD'])
×
522

523
    def _get_unique_reference_fluxes(self):
1✔
524
        """
525
        :return: Get the unique reference fluxes for each filter in the survey
526
        """
527
        unique_bands = self.pointings_database.filters.unique()
×
528
        ref_flux = redback.utils.bands_to_reference_flux(unique_bands)
×
529
        return ref_flux
×
530

531
    def _make_sncosmo_wrapper_for_user_model(self):
1✔
532
        """
533

534
        Function to wrap user models into sncosmo model format for full functionality.
535
        :return: sncosmo source
536
        """
537
        # Ensure sncosmo_kwargs is a dictionary
NEW
538
        if self.sncosmo_kwargs is None:
×
NEW
539
            self.sncosmo_kwargs = {}
×
540

541
        self.sncosmo_kwargs['max_time'] = self.sncosmo_kwargs.get('max_time', 100)
×
542
        self.parameters['wavelength_observer_frame'] = self.parameters.get('wavelength_observer_frame',
×
543
                                                                          np.geomspace(100,60000,100))
544
        time = self.sncosmo_kwargs.get('dense_times', np.linspace(0, self.sncosmo_kwargs['max_time'], 200))
×
545
        fmjy = self.model(time, **self.parameters)
×
546
        spectra = fmjy.to(uu.mJy).to(uu.erg / uu.cm ** 2 / uu.s / uu.Angstrom,
×
547
                equivalencies=uu.spectral_density(wav=self.parameters['wavelength_observer_frame'] * uu.Angstrom))
548
        source = TimeSeriesSource(phase=time, wave=self.parameters['wavelength_observer_frame'],
×
549
                                  flux=spectra)
550
        return source
×
551

552
    def _survey_to_table_name_lookup(self, survey):
1✔
553
        """
554

555
        A lookup table to get the name of the pointings database for a given survey.
556
        :param survey: name of the survey
557
        :return: path to the pointings database
558
        """
559
        survey_to_table = {'Rubin_10yr_baseline': 'rubin_baseline_v3.0_10yrs.tar.gz',
1✔
560
                           'Rubin_10yr_morez': 'rubin_morez.tar.gz',
561
                           'Rubin_10yr_lessweight': 'rubin_lessweight.tar.gz',
562
                           'ztf': 'ztf.tar.gz',
563
                           'roman': 'roman.tar.gz'}
564
        return survey_to_table[survey]
1✔
565

566
    def _find_time_overlaps(self):
1✔
567
        """
568
        Find the time indices of the pointings database that overlap with the transient.
569

570
        :return: indices of the pointings database that overlap with the transient.
571
        """
572
        pointing_times = self.pointings_database[['expMJD']].values.flatten()
1✔
573
        if self.population:
1✔
574
            condition_1 = pointing_times >= self.t0_transient.values[:, None] - self.obs_buffer
×
575
            condition_2 = pointing_times <= self.end_transient_time.values[:, None]
×
576
        else:
577
            condition_1 = pointing_times >= self.t0_transient.values - self.obs_buffer
1✔
578
            condition_2 = pointing_times <= self.end_transient_time.values
1✔
579
        mask = np.logical_and(condition_1, condition_2)
1✔
580
        if self.population:
1✔
581
            return mask
×
582
        else:
583
            time_indices = np.where(mask)
1✔
584
            return time_indices[0]
1✔
585

586

587
    def _find_sky_overlaps(self):
1✔
588
        """
589
        Find the sky indices of the pointings database that overlap with the transient.
590
        """
591
        pointings_sky_pos = np.column_stack((self.pointings_database['_ra'].values, self.pointings_database['_dec'].values))
1✔
592
        transient_sky_pos = np.column_stack((self.parameters['ra'].values, self.parameters['dec'].values))
1✔
593

594
        transient_sky_pos_3D = np.vstack([np.cos(transient_sky_pos[:,0]) * np.cos(transient_sky_pos[:,1]),
1✔
595
                                          np.sin(transient_sky_pos[:,0]) * np.cos(transient_sky_pos[:,1]),
596
                                          np.sin(transient_sky_pos[:,1])]).T
597
        pointings_sky_pos_3D = np.vstack([np.cos(pointings_sky_pos[:, 0]) * np.cos(pointings_sky_pos[:,1]),
1✔
598
                                          np.sin(pointings_sky_pos[:,0]) * np.cos(pointings_sky_pos[:,1]),
599
                                          np.sin(pointings_sky_pos[:,1])]).T
600
        # law of cosines to compute 3D distance
601
        max_3D_dist = np.sqrt(2. - 2. * np.cos(self.survey_radius))
1✔
602
        survey_tree = KDTree(pointings_sky_pos_3D)
1✔
603
        if self.population:
1✔
604
            overlap_indices = survey_tree.query_ball_point(x=transient_sky_pos_3D, r=max_3D_dist)
×
605
        else:
606
            overlap_indices = survey_tree.query_ball_point(x=transient_sky_pos_3D.T.flatten(), r=max_3D_dist)
1✔
607
        return overlap_indices
1✔
608

609

610
    def _make_observation_single(self, overlapping_database, t0_transient, sncosmo_model):
1✔
611
        """
612
        Calculate properties of the transient at the overlapping pointings for a single transient.
613

614
        :param overlapping_database:
615
        :return: Dataframe of observations including non-detections/upper limits
616
        """
617
        times = overlapping_database['expMJD'].values - t0_transient
1✔
618
        filters = overlapping_database['filter'].values
1✔
619
        magnitude = sncosmo_model.bandmag(phase=times, band=filters, magsys='AB')
1✔
620
        flux = redback.utils.bandpass_magnitude_to_flux(magnitude=magnitude, bands=filters)
1✔
621
        ref_flux = redback.utils.bands_to_reference_flux(filters)
1✔
622
        bandflux_errors = redback.utils.bandflux_error_from_limiting_mag(overlapping_database['fiveSigmaDepth'].values,
1✔
623
                                                                         ref_flux)
624
        if self.add_source_noise:
1✔
625
            bandflux_errors = np.sqrt(bandflux_errors**2 + self.source_noise_factor*flux**2)
×
626
        # what can be preprocessed
627
        observed_flux = np.random.normal(loc=flux, scale=bandflux_errors)
1✔
628
        magnitudes = redback.utils.bandpass_flux_to_magnitude(observed_flux, filters)
1✔
629
        magnitude_errs = redback.utils.magnitude_error_from_flux_error(flux, bandflux_errors)
1✔
630
        flux_density = calc_flux_density_from_ABmag(magnitude).value
1✔
631

632
        observation_dataframe = pd.DataFrame()
1✔
633
        observation_dataframe['time'] = overlapping_database['expMJD'].values
1✔
634
        observation_dataframe['magnitude'] = magnitudes
1✔
635
        observation_dataframe['e_magnitude'] = magnitude_errs
1✔
636
        observation_dataframe['band'] = filters
1✔
637
        observation_dataframe['system'] = 'AB'
1✔
638
        observation_dataframe['flux_density(mjy)'] = flux_density
1✔
639
        observation_dataframe['flux_density_error'] = calc_flux_density_error_from_monochromatic_magnitude(
1✔
640
            magnitude=magnitude, magnitude_error=magnitude_errs, reference_flux=3631)
641
        observation_dataframe['flux(erg/cm2/s)'] = observed_flux
1✔
642
        observation_dataframe['flux_error'] = bandflux_errors
1✔
643
        observation_dataframe['time (days)'] = times
1✔
644
        mask = (observation_dataframe['time (days)'] <= 0.) | (np.isnan(observation_dataframe['magnitude']))
1✔
645
        snr = observed_flux/bandflux_errors
1✔
646
        mask_snr = snr < self.snr_threshold
1✔
647
        detected = np.ones(len(observation_dataframe))
1✔
648
        detected[mask] = 0
1✔
649
        detected[mask_snr] = 0
1✔
650
        observation_dataframe['detected'] = detected
1✔
651
        observation_dataframe['limiting_magnitude'] = overlapping_database['fiveSigmaDepth'].values
1✔
652
        return observation_dataframe
1✔
653

654
    def _make_observations(self):
1✔
655
        """
656
        Calculate properties of the transient at the overlapping pointings for a single transient.
657
        :return: Dataframe of observations including non-detections/upper limits
658
        """
659
        overlapping_sky_indices = self._find_sky_overlaps()
1✔
660
        overlapping_time_indices = self._find_time_overlaps()
1✔
661

662
        space_set = set(overlapping_sky_indices)
1✔
663
        time_set = set(overlapping_time_indices)
1✔
664
        time_space_overlap = list(space_set.intersection(time_set))
1✔
665
        overlapping_database_iter = self.pointings_database.iloc[time_space_overlap]
1✔
666
        overlapping_database_iter = overlapping_database_iter.sort_values(by=['expMJD'])
1✔
667
        dataframe = self._make_observation_single(overlapping_database_iter,
1✔
668
                                                  t0_transient=self.t0_transient.values,
669
                                                  sncosmo_model=self.sncosmo_model)
670
        return dataframe
1✔
671

672
    def _make_observations_for_population(self):
1✔
673
        """
674
        Calculate properties of the transient at the overlapping pointings for a transient population.
675
        :return: Dataframe of observations including non-detections/upper limits
676
        """
677
        dfs = []
×
678
        overlapping_sky_indices = self._find_sky_overlaps()
×
679
        time_mask = self._find_time_overlaps()
×
680
        for x in range(len(self.parameters)):
×
681
            overlapping_time_indices = np.where(time_mask[x])[0]
×
682
            space_set = set(overlapping_sky_indices[x])
×
683
            time_set = set(overlapping_time_indices)
×
684
            time_space_overlap = list(space_set.intersection(time_set))
×
685
            overlapping_database_iter = self.pointings_database.iloc[time_space_overlap]
×
686
            overlapping_database_iter = overlapping_database_iter.sort_values(by=['expMJD'])
×
687
            dataframe = self._make_observation_single(overlapping_database_iter,
×
688
                                                      t0_transient=self.t0_transient.iloc[x],
689
                                                      sncosmo_model=self.all_sncosmo_models[x])
690
            dfs.append(dataframe)
×
691
        return dfs
×
692

693

694
    def save_transient_population(self, transient_names=None, **kwargs):
1✔
695
        """
696
        Save the transient population to a csv file.
697
        This will save the full observational dataframe including non-detections etc.
698
        This will save the data to a folder called 'simulated'
699
        with the name of the transient and a csv file of the injection parameters
700

701
        :param transient_names: list of transient names. Default is None which will label transients as event_0, etc
702
        :param kwargs: kwargs for the save_transient function
703
        :param injection_file_path: path to save the injection file
704
        :return: None
705
        """
706
        injection_file_name = kwargs.get('injection_file_path', 'simulated/population_injection_parameters.csv')
×
707
        if transient_names is None:
×
708
            transient_names = ['event_' + str(x) for x in range(len(self.list_of_observations))]
×
709
        bilby.utils.check_directory_exists_and_if_not_mkdir('simulated')
×
710
        self.parameters.to_csv(injection_file_name, index=False)
×
711
        for ii, transient_name in enumerate(transient_names):
×
712
            transient = self.list_of_observations[ii]
×
713
            transient.to_csv('simulated/' + transient_name + '.csv', index=False)
×
714

715
    def save_transient(self, name):
1✔
716
        """
717
        Save the transient observations to a csv file.
718
        This will save the full observational dataframe including non-detections etc.
719
        This will save the data to a folder called 'simulated'
720
        with the name of the transient and a csv file of the injection parameters
721

722
        :param name: name to save transient.
723
        """
724
        bilby.utils.check_directory_exists_and_if_not_mkdir('simulated')
×
725
        path = 'simulated/' + name + '.csv'
×
726
        injection_path = 'simulated/' + name + '_injection_parameters.csv'
×
727
        self.observations.to_csv(path, index=False)
×
728
        self.parameters.to_csv(injection_path, index=False)
×
729

730
def make_pointing_table_from_average_cadence(ra, dec, num_obs, average_cadence, cadence_scatter, limiting_magnitudes, **kwargs):
1✔
731
    """
732
    Makes a pandas dataframe of pointings from specified settings.
733

734
    :param float: ra
735
    :param float: dec
736
    :param dict: num_obs
737
    :param dict: average_cadence
738
    :param dict: cadence_scatter
739
    :param dict: limiting_magnitudes
740

741
    :return dataframe: pandas dataframe of the mock pointings needed to simulate observations for
742
    given transient.
743
    """
744
    initMJD = kwargs.get("initMJD", 59580)
1✔
745
    df_list = []
1✔
746
    for band in average_cadence.keys():
1✔
747
        expMJD = initMJD + np.cumsum(np.random.normal(loc=average_cadence[band], scale=cadence_scatter[band], size=num_obs[band]))
1✔
748
        filters = list(repeat(band, num_obs[band]))
1✔
749
        limiting_mag = list(repeat(limiting_magnitudes[band], num_obs[band]))
1✔
750
        ras = np.ones(num_obs[band])*ra
1✔
751
        decs = np.ones(num_obs[band])*dec
1✔
752
        band_pointings_dataframe = pd.DataFrame.from_dict({'expMJD': expMJD, '_ra': ras, '_dec': decs, 'filter': filters, 'fiveSigmaDepth': limiting_mag})
1✔
753
        df_list.append(band_pointings_dataframe)
1✔
754
    pointings_dataframe = pd.concat(df_list)
1✔
755
    pointings_dataframe.sort_values('expMJD', inplace=True)
1✔
756
    return pointings_dataframe
1✔
757

758
class SimulateFullOpticalSurvey(SimulateOpticalTransient):
1✔
759
    def __init__(self, model, prior, rate, survey_start_date, survey_duration, pointings_database=None,
1✔
760
                 survey='Rubin_10yr_baseline',sncosmo_kwargs=None, obs_buffer=5.0, survey_fov_sqdeg=9.6,
761
                 snr_threshold=5, end_transient_time=1000, add_source_noise=False, model_kwargs=None, **kwargs):
762
        """
763
        Simulate a full optical survey. This requires a rate and a prior for the population.
764
        The rate is used to draw events in a period of time, placing them isotropically on the sky and uniform in comoving volume.
765
        The prior is used to draw the parameters of the individual events.
766
        We can then simulate observations of all these events and understand the rate of detections etc.
767

768
        :param model: String corresponding to redback model or a python function that can evaluate an SED.
769
        :param prior: A redback prior corresponding to the model.
770
            The prior on the redshift is forced to be uniform in comoving volume. With maximum what the user sets in their prior.
771
        :param rate: Rate of the population in Gpc^-3 yr^-1
772
        :param survey_start_date: Start date of the survey in MJD.
773
        :param survey_duration: Duration of the survey in years.
774
            This can be set arbitrarily high if one wants to look at detection efficiencies.
775
            Or to a real number if wanting to look at a volume/flux limited survey.
776
        :param pointings_database: A pandas DataFrame containing the pointings of the survey.
777
        :param survey: String corresponding to the survey name. This is used to look up the pointings database.
778
        :param sncosmo_kwargs: Any kwargs to be passed to SNcosmo.
779
            SNcosmo is used to evaluate the bandpass magnitudes in different bands.
780
        :param obs_buffer: A observation buffer in days to add to the start of the transient
781
            to allow for non-detections. Default is 5 days
782
        :param snr_threshold: SNR threshold for detection. Default is 5.
783
        :param end_transient_time: End time of the transient in days. Default is 1000 days.
784
            Note that SNCosmo will extrapolate past when the transient model evaluates the SED so these should really be the same.
785
        :param add_source_noise: Boolean. If True, add an extra noise in quadrature to the limiting mag noise.
786
            The factor is a multiple of the model flux i.e. noise = (skynoise**2 + (model_flux*source_noise)**2)**0.5
787
        :param model_kwargs: Dictionary of kwargs to be passed to the model.
788
        :param kwargs: Dictionary of additional kwargs
789
        :param cosmology: Cosmology to use. Default is Planck18.
790
            Users can pass their own cosmology class here as long as it works like astropy.cosmology.
791
            Users should ensure they use the same cosmology in the model. Or deliberately choose not to.
792
        :param source_noise: Float. Factor to multiply the model flux by to add an extra noise
793
            in quadrature to the limiting mag noise. Default value is 0.02, disabled by default.
794
        """
795
        self.rate = rate * uu.Gpc**-3 * uu.yr**-1
1✔
796
        self.prior = prior
1✔
797
        self.survey_start_date = survey_start_date
1✔
798
        self.survey_duration = survey_duration * uu.yr
1✔
799
        cosmology = kwargs.get('cosmology',cosmo)
1✔
800
        self.horizon_redshift = self.prior['redshift'].maximum
1✔
801
        self.horizon_distance = cosmology.luminosity_distance(self.horizon_redshift).to(uu.Mpc)
1✔
802
        self.number_of_events = np.random.poisson(self.rate_per_sec * self.survey_duration_seconds)
1✔
803
        self.prior['redshift'] = bilby.gw.prior.UniformSourceFrame(minimum=0, maximum=self.horizon_redshift,
1✔
804
                                                                   name='redshift', cosmology='Planck18')
805
        self.prior['ra'] = bilby.core.prior.Uniform(minimum=0, maximum=2*np.pi, name='ra', latex_label='$\\mathrm{RA}$',
1✔
806
                                   unit=None, boundary='periodic')
807
        self.prior['dec'] = bilby.core.prior.Cosine(minimum=-np.pi/2, maximum=np.pi/2., name='dec',
1✔
808
                                  latex_label='$\\mathrm{DEC}$', unit=None, boundary=None)
809
        parameters = prior.sample(self.number_of_events)
1✔
810
        _ra = self.prior['ra'].sample(self.number_of_events)
1✔
811
        _dec = self.prior['dec'].sample(self.number_of_events)
1✔
812
        _event_times = self.get_event_times()
1✔
813
        parameters['ra'] = _ra
1✔
814
        parameters['dec'] = _dec
1✔
815
        parameters['t0_mjd_transient'] = _event_times
1✔
816
        super().__init__(model=model, parameters=parameters, pointings_database=pointings_database,
1✔
817
                         survey=survey, sncosmo_kwargs=sncosmo_kwargs,
818
                         obs_buffer=obs_buffer,survey_fov_sqdeg=survey_fov_sqdeg,
819
                         snr_threshold=snr_threshold, end_transient_time=end_transient_time,
820
                         add_source_noise=add_source_noise,
821
                         population=True, model_kwargs=model_kwargs, **kwargs)
822

823
    @property
1✔
824
    def rate_per_sec(self):
1✔
825
        rate_per_year = self.rate * self.horizon_distance.to(uu.Gpc)**3 * 4./3. * np.pi
1✔
826
        return rate_per_year.to(uu.s**-1)
1✔
827

828
    @property
1✔
829
    def survey_duration_seconds(self):
1✔
830
        return self.survey_duration.to(uu.s)
1✔
831

832
    @property
1✔
833
    def time_window(self):
1✔
834
        time_window = [self.survey_start_date, self.survey_start_date + self.survey_duration.to(uu.day).value]
1✔
835
        return time_window
1✔
836

837
    def get_event_times(self):
1✔
838
        if self.number_of_events == 1:
1✔
839
            event_id = random.random() * self.survey_duration_seconds.to(uu.day).value + self.survey_start_date
×
840
        elif self.number_of_events > 1:
1✔
841
            event_id = []
1✔
842
            for j in range(self.number_of_events):
1✔
843
                event_id.append(random.random())
1✔
844
            event_id.sort()
1✔
845
            event_id = np.array(event_id)
1✔
846
            for j in range(self.number_of_events):
1✔
847
                event_id[j] *= self.survey_duration_seconds.to(uu.day).value
1✔
848
                event_id[j] += self.survey_start_date
1✔
849
        else:
850
            event_id = [self.survey_start_date]
×
851
        return event_id
1✔
852

853
    def save_survey(self, survey=None, **kwargs):
1✔
854
        """
855
        Save the transient population to a csv file.
856
        This will save the full observational dataframe including non-detections etc.
857
        This will save the data to a folder called 'simulated'
858
        with the name of the transient and a csv file of the injection parameters
859

860
        :param transient_names: list of transient names. Default is None which will label transients as event_0, etc
861
        :param kwargs: kwargs for the save_transient function
862
        :param injection_file_path: path to save the injection file
863
        :return: None
864
        """
865
        injection_file_name = kwargs.get('injection_file_path', 'simulated_survey/population_injection_parameters.csv')
1✔
866
        if survey is None:
1✔
867
            survey_name = 'survey'
1✔
868
        else:
869
            survey_name = survey
×
870
        transient_names = [survey_name + '_event_' + str(x) for x in range(len(self.list_of_observations))]
1✔
871
        bilby.utils.check_directory_exists_and_if_not_mkdir('simulated_survey')
1✔
872
        self.parameters.to_csv(injection_file_name, index=False)
1✔
873
        for ii, transient_name in enumerate(transient_names):
1✔
874
            transient = self.list_of_observations[ii]
1✔
875
            transient.to_csv('simulated_survey/' + transient_name + '.csv', index=False)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc