ACID-code 2.0.0a2__py3-none-any.whl → 2.0.0a4__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.
ACID_code/acid.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+ import traceback
2
3
  import warnings
3
4
  warnings.filterwarnings("ignore")
4
5
  import sys, emcee, os, time, inspect, inspect, contextlib
@@ -15,6 +16,10 @@ from .result import Result
15
16
  from .data import Data, Config, MaskingLines, LineList, DataList
16
17
  from .errors import ContinuumError
17
18
  from .utils import IntLike, Scalar, Array1D, Array2D
19
+ try:
20
+ import dynesty
21
+ except ImportError:
22
+ dynesty = None
18
23
 
19
24
  @beartype
20
25
  class Acid:
@@ -55,9 +60,9 @@ class Acid:
55
60
 
56
61
  Important note: All defaults in the signature are None, meaning if any values are input, they will override the default :py:class:`Config` and/or :py:class:`Data` values or
57
62
  any values that have already been input. The defaults within the config are written below. The config defaults can also be accessed via
58
- :py:attr:`ACID_code.Config.defaults` (returning a dictionary of defaults for both initialisation and run_acid).
63
+ :py:attr:`ACID_code.Config.defaults` (returning a dictionary of defaults for both initialisation and the ACID method).
59
64
 
60
- All parameters below and in run_ACID are stored in the :py:class:`Config` instance, unless explicitly stated to be in the :py:class:`Data` instance.
65
+ All parameters below and in the ACID method are stored in the :py:class:`Config` instance, unless explicitly stated to be in the :py:class:`Data` instance.
61
66
  The :py:class:`Config` instance is for runtime settings and the :py:class:`Data` instance is for storing data and any calculations.
62
67
 
63
68
  Parameters
@@ -68,12 +73,11 @@ class Acid:
68
73
  choose your own velocity grid, by default None, stored in the Data instance.
69
74
  linelist : :py:type:`Array2D | str` | :py:class:`LineList` | dict`, optional
70
75
  The linelist to use for LSD. The linelist should have wavelengths in angstroms and relative depths between 0 and 1.
71
- This is a required parameter if linelist_wl and linelist_depths are not provided. It can be of the forms:
76
+ This is a required parameter. It can be of the forms:
72
77
  - String: A path to a VALD linelist in string format. Support for other linelists may be added in the future or on request.
73
78
  - :py:type:`Array2D`: A 2D array-like object indexed such that 0 is wavelengths and 1 is depths.
74
79
  - dict: A dictionary with keys "wavelengths" and "depths", each containing array-like objects for the wavelengths and depths respectively.
75
80
  - :py:class:`LineList`: The :py:class:`LineList` class is used to expose the linelist for masking or getting/plotting the linelist. You can input an instance if you have one.
76
- - If None, linelist_wl and linelist_depths must be provided (see below), by default None, stored in the Data instance.
77
81
  order : :py:type:`IntLike`, optional
78
82
  If this ACID instance is intended as a run on a specific order, then you can designate this instance for that order. This will allow
79
83
  the resulting Data instance to track of which order the profiles correspond to. Note that orders can be indexed by the correct indexing
@@ -96,7 +100,7 @@ class Acid:
96
100
  By default 2 (medium).
97
101
  sampler_progress : :py:type:`bool`, optional
98
102
  A verbosity override for just the MCMC sampling progress.
99
- By default None which does not override, but if True/False, it will overwrite with that value.
103
+ By default None which does not override, but if True/False, it will overwrite with that value, and use/don't use a tqdm output for the sampler.
100
104
  masking_lines : :py:type:`dict` | :py:class:`MaskingLines`, optional
101
105
  Telluric lines (in angstroms) and widths in (km/s) to mask from the wavelength regions from. Unless you'd like to change the default masking
102
106
  lines, we recommend just using the defaults (leaving this as None), which are based on telluric lines and strong hydrogen/metal lines in the
@@ -112,7 +116,7 @@ class Acid:
112
116
  The path to save the sampler HDF5 backend file to.
113
117
  If None, the sampler is not saved and only stored in memory. By default None.
114
118
  Note that if your path points to an existing file, it will be overwritten on Acid initialization.
115
- If True, we use the emcee HDF5 backend to store and load the sampler.
119
+ If existing, we use the emcee HDF5 backend to store and load the sampler.
116
120
  Should be a valid file path that ends with ".h5". If the directory containing it does not exist, it will be created.
117
121
  Note that if you later try and save the sampler through the data class, it is converted to a HD5 backend.
118
122
  data : :py:class:`Data` | :py:class:`DataList`, optional
@@ -231,6 +235,8 @@ class Acid:
231
235
  dev_perc : IntLike|None = None, # Config
232
236
  n_sig : IntLike|None = None, # Config
233
237
  skips : IntLike|None = None, # Config
238
+ od : bool|None = None, # Config
239
+ sampler_type : str|None = None, # Config
234
240
  parallel : bool|None = None, # Config
235
241
  cores : IntLike|None = None, # Config
236
242
  nwalkers : IntLike|None = None, # Config, then Data just before MCMC
@@ -308,6 +314,14 @@ class Acid:
308
314
  skips : :py:type:`IntLike`, optional
309
315
  An option to only run acid on one in every n pixels, where n is the integer argument. This is only useful for
310
316
  testing to get a quicker result especially for larger wavelength ranges or datasets, by default 1 (no skipping)
317
+ od : :py:type:`bool`, optional
318
+ If True, runs ACID in optical depth, otherwise, the LSD methods and ACID fitting is performed in flux. By default None which defaults to True.
319
+ Note that the whole point of ACID is to run LSD in OD, we highly recommend leaving this unless you specifically want to compare.
320
+ sampler_type : :py:type:`str`, optional
321
+ If you really try to wish to use the dynesty nested sampler, you can set this to "dynesty". It is almost entirely unsupported
322
+ by the rest of the code other than to just get a finished result object, and much slower. We highly recommend using None or "emcee" (default).
323
+ The only reason I added this was to get the Bayesian evidence for model comparison.
324
+ If "dynesty" is chosen, the dynesty package needs to be installed, and the nsteps parameter is treated as "nlive" to be passed to the NestedSampler.
311
325
  parallel : :py:type:`bool`, optional
312
326
  If True uses multiprocessing to calculate the profiles for each frame in parallel, see
313
327
  https://acid-code.readthedocs.io/en/stable/using_ACID.html#multiprocessing for more details. By default True
@@ -411,6 +425,8 @@ class Acid:
411
425
  "dev_perc" : dev_perc,
412
426
  "n_sig" : n_sig,
413
427
  "skips" : skips,
428
+ "od" : od,
429
+ "sampler_type" : sampler_type,
414
430
  "parallel" : parallel,
415
431
  "cores" : cores,
416
432
  "nwalkers" : nwalkers,
@@ -435,6 +451,14 @@ class Acid:
435
451
  print("Parallel MCMC on Windows is not currently supported. Running MCMC serially.")
436
452
  self.config.parallel = False
437
453
 
454
+ if self.config.sampler_type == "dynesty":
455
+ if dynesty is None:
456
+ raise ImportError("The 'dynesty' sampler requires the 'dynesty' package to be installed.\nPlease install it with 'pip install dynesty' or choose a different sampler type.")
457
+ if self.config.sampler_type == "dynesty" and not self.config.deterministic_profile:
458
+ raise ValueError("The 'dynesty' sampler can only be run with deterministic_profile=True (otherwise you'll be waiting hours for a single result)")
459
+ if self.config.sampler_type == "dynesty" and self.config.max_steps is not None:
460
+ raise ValueError("Cannot use max_steps as dynesty already natively supports this with live points, set nsteps=nlive. See the dynesty docs for more details.")
461
+
438
462
  # --- Start of the ACID method ---
439
463
 
440
464
  # Setup and data validation done in data class and applies skips
@@ -484,8 +508,9 @@ class Acid:
484
508
  # The code for telluric masking is contained without the MaskingLines class, which both telluric_lines
485
509
  # and hydrogen_lines are instances of.
486
510
  line_mask = self.config.masking_lines.get_masks(self.data.wavelengths["combined"])
487
- line_mask = np.all(line_mask, axis=0)
488
- self.data.errors["combined"][line_mask] = 1e12
511
+ if line_mask != []:
512
+ line_mask = np.all(line_mask, axis=0)
513
+ self.data.errors["combined"][line_mask] = 1e12
489
514
 
490
515
  # Get the initial polynomial coefficents
491
516
  if not hasattr(self.data.wavelengths, "combined_normalized"):
@@ -558,22 +583,25 @@ class Acid:
558
583
  self.data.nwalkers = self.data.ndim * 3 if self.config.nwalkers is None else self.config.nwalkers
559
584
  rng = np.random.default_rng(self.config.seed)
560
585
 
561
- # Starting values of walkers with independent variation
562
- sigma = 0.8 * 0.005
563
- initial_state = []
564
- for i in range(0, len(self.data.model_inputs)):
565
- if i < len(self.data.velocities):
566
- if not self.config.deterministic_profile:
567
- pos = rng.normal(self.data.model_inputs[i], sigma, (self.data.nwalkers, ))
586
+ # Starting values of walkers with independent variation#
587
+ if self.config.sampler_type == "emcee":
588
+ sigma = 0.8 * 0.005
589
+ initial_state = []
590
+ for i in range(0, len(self.data.model_inputs)):
591
+ if i < len(self.data.velocities):
592
+ if not self.config.deterministic_profile:
593
+ pos = rng.normal(self.data.model_inputs[i], sigma, (self.data.nwalkers, ))
594
+ else:
595
+ continue
568
596
  else:
569
- continue
570
- else:
571
- x1 = self.data.model_inputs[i]
572
- rounded_sigma = round(x1, 1-int(floor(log10(abs(x1))))-1)
573
- sigma = abs(rounded_sigma) / 10
574
- pos = rng.normal(self.data.model_inputs[i], sigma, (self.data.nwalkers, ))
575
- initial_state.append(pos)
576
- initial_state = np.array(initial_state).T
597
+ x1 = self.data.model_inputs[i]
598
+ rounded_sigma = round(x1, 1-int(floor(log10(abs(x1))))-1)
599
+ sigma = abs(rounded_sigma) / 10
600
+ pos = rng.normal(self.data.model_inputs[i], sigma, (self.data.nwalkers, ))
601
+ initial_state.append(pos)
602
+ initial_state = np.array(initial_state).T
603
+ else:
604
+ initial_state = None
577
605
 
578
606
  ### ACID initialialised ###
579
607
  self.data.setup_time += time.time() - init_t0
@@ -617,12 +645,12 @@ class Acid:
617
645
  """
618
646
  This method is no longer supported in ACID. Please use the ACID function with the appropriate inputs for HARPS spectra instead.
619
647
  Future versions of ACID will provide functions to load and configure data from a range of different standard instruments.
620
- If you still really wish to use ACID_HARPS, the last stable version of ACID with the method is 1.4.5. Try: pip install ACID_code==1.4.5
648
+ If you still really wish to use ACID_HARPS, the last stable version of ACID with the method is 1.4.5. Try: pip install ACID_code_v2==1.4.5
621
649
  """
622
650
  raise NotImplementedError(f"ACID_HARPS is no longer supported in ACID. \n"
623
651
  f"Please use the ACID function with the appropriate inputs for HARPS spectra instead. \n"
624
652
  f"Future versions of ACID will provide functions to load and configure data from a range of different standard instruments. \n"
625
- f"If you still really wish to use ACID_HARPS, the last stable version of ACID with the method is 1.4.5. Try: pip install ACID_code==1.4.5")
653
+ f"If you still really wish to use ACID_HARPS, the last stable version of ACID with the method is 1.4.5. Try: pip install ACID_code_v2==1.4.5")
626
654
 
627
655
  def combine_spec(
628
656
  self,
@@ -834,9 +862,12 @@ class Acid:
834
862
  self.data.plot_continuum_fit(plot_type=plot_type)
835
863
 
836
864
  if np.any(flux_obs <= 0) or np.any(new_errors <= 0):
837
- raise ContinuumError("Continuum fit resulted in non-positive flux or errors, which is not physical.\n " \
865
+ error = ContinuumError("Continuum fit resulted in non-positive flux or errors, which is not physical.\n " \
838
866
  "Consider adjusting the polynomial order or continuum percentile. Use verbose=3 to see the plot of the continuum fit.\n " \
839
867
  "Note that this will only work for interactive terminals or displays which work with plt.show()")
868
+ self.data.exception = error
869
+ self.data.traceback = traceback.format_stack()
870
+ raise error
840
871
 
841
872
  return poly_coeffs, flux_obs, new_errors
842
873
 
@@ -856,7 +887,7 @@ class Acid:
856
887
  sn = self.data.sn["combined"]
857
888
 
858
889
  # Use the initial LSD run to get the forward model and scaled residuals
859
- forward, _profile = mcmc.MCMC(x, y, yerr, self.data.alpha).full_model(self.data.model_inputs)
890
+ forward, _profile = mcmc.MCMC(x, y, yerr, self.data.alpha, od=self.config.od).full_model(self.data.model_inputs)
860
891
  residuals = (y - forward) / forward
861
892
 
862
893
  # Chunk masking based on deviation from residuals
@@ -953,7 +984,8 @@ class Acid:
953
984
  """
954
985
 
955
986
  # Get default sampler kwargs from initial state
956
- sampler_kwargs, mcmc_kwargs = self._get_sampler_kwargs(nsteps, state)
987
+ if self.config.sampler_type == "emcee":
988
+ sampler_kwargs, mcmc_kwargs = self._get_sampler_kwargs(nsteps, state)
957
989
  pool_context = nullcontext(None)
958
990
 
959
991
  if self.config.parallel:
@@ -964,14 +996,25 @@ class Acid:
964
996
 
965
997
  ctx = mp.get_context("fork")
966
998
  pool_context = ctx.Pool(processes=self.config.cores, initializer=mcmc._mp_init_worker, initargs=(self.data,))
967
- log_prob_fn = mcmc._mp_log_probability
999
+ log_prob = mcmc._mp_log_probability if self.config.sampler_type == "emcee" else mcmc._mp_log_likelihood
1000
+ ptform = mcmc._mp_ptform
1001
+ queue_size = os.cpu_count()
968
1002
  else:
969
1003
  MCMC = mcmc.MCMC(self.data)
970
- log_prob_fn = MCMC
971
-
1004
+ log_prob = MCMC if self.config.sampler_type == "emcee" else MCMC.dynesty_logprob
1005
+ ptform = MCMC.ptform
1006
+ queue_size = None
1007
+
972
1008
  with pool_context as pool:
973
- self.sampler = EnsembleSampler(log_prob_fn=log_prob_fn, pool=pool, **sampler_kwargs)
974
- self.sampler.run_mcmc(**mcmc_kwargs)
1009
+ if self.config.sampler_type == "emcee":
1010
+ self.sampler = EnsembleSampler(log_prob_fn=log_prob, pool=pool, **sampler_kwargs)
1011
+ self.sampler.run_mcmc(**mcmc_kwargs)
1012
+ else:
1013
+ import dynesty
1014
+ if self.config.parallel:
1015
+ pool.size = self.config.cores
1016
+ self.sampler = dynesty.NestedSampler(log_prob, ptform, self.data.ndim, self.config.nsteps, pool=pool, queue_size=queue_size)
1017
+ self.sampler.run_nested(print_progress=self.config.verbose>1)
975
1018
 
976
1019
  def run_mcmc_until_converged(self, max_steps:IntLike, state=None) -> None:
977
1020
  """