ACID-code 1.5.0a3__py3-none-any.whl → 1.5.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/data.py +86 -5
- ACID_code/load.py +34 -6
- ACID_code/lsd.py +3 -3
- ACID_code/profiles.py +2 -2
- ACID_code/result.py +8 -12
- ACID_code/utils.py +2 -2
- {acid_code-1.5.0a3.dist-info → acid_code-1.5.0a4.dist-info}/METADATA +1 -1
- acid_code-1.5.0a4.dist-info/RECORD +14 -0
- acid_code-1.5.0a3.dist-info/RECORD +0 -14
- {acid_code-1.5.0a3.dist-info → acid_code-1.5.0a4.dist-info}/WHEEL +0 -0
- {acid_code-1.5.0a3.dist-info → acid_code-1.5.0a4.dist-info}/licenses/LICENSE +0 -0
- {acid_code-1.5.0a3.dist-info → acid_code-1.5.0a4.dist-info}/top_level.txt +0 -0
ACID_code/data.py
CHANGED
|
@@ -581,6 +581,10 @@ class Data:
|
|
|
581
581
|
# Make any values < 0 or infinite equal to nan, which are gracefully later handled.
|
|
582
582
|
input_wavelengths, input_flux, input_errors = utils.mask_invalid(input_wavelengths, input_flux, input_errors, verbose=self.config.verbose)
|
|
583
583
|
|
|
584
|
+
# Check that none of the inputs are all nan
|
|
585
|
+
if np.all(np.isnan(input_wavelengths)) or np.all(np.isnan(input_flux)) or np.all(np.isnan(input_errors)):
|
|
586
|
+
raise ValueError("Any of the input wavelengths, spectra, and errors cannot be all NaN.")
|
|
587
|
+
|
|
584
588
|
# Guess sn if input_sn not provided
|
|
585
589
|
if input_sn is None:
|
|
586
590
|
input_sn = utils.guess_SNR(input_wavelengths, input_flux, input_errors)
|
|
@@ -767,8 +771,10 @@ class DataList:
|
|
|
767
771
|
data_list = [data_list]
|
|
768
772
|
|
|
769
773
|
if verbose is not None:
|
|
770
|
-
|
|
771
|
-
|
|
774
|
+
old_verbose = np.copy(data_list[0].config.verbose)
|
|
775
|
+
data_list[0].config.verbose = verbose # verbose in config is a property and has good validation
|
|
776
|
+
self.verbose = np.copy(data_list[0].config.verbose)
|
|
777
|
+
data_list[0].config.verbose = old_verbose # reset to old verbose
|
|
772
778
|
else:
|
|
773
779
|
self.verbose = np.max([data.config.verbose for data in data_list])
|
|
774
780
|
|
|
@@ -838,6 +844,82 @@ class DataList:
|
|
|
838
844
|
if len(np.unique(self.orders)) != len(self.orders):
|
|
839
845
|
raise ValueError("All Data instances within the inputted list must have unique order numbers.")
|
|
840
846
|
|
|
847
|
+
def run_ACID(
|
|
848
|
+
self,
|
|
849
|
+
orders : Array1D|int|str|None = None,
|
|
850
|
+
store_sampler : bool = True
|
|
851
|
+
) -> None:
|
|
852
|
+
from .acid import Acid
|
|
853
|
+
|
|
854
|
+
if isinstance(orders, int):
|
|
855
|
+
orders = [orders]
|
|
856
|
+
elif isinstance(orders, str):
|
|
857
|
+
if orders == "all":
|
|
858
|
+
orders = self.orders
|
|
859
|
+
else:
|
|
860
|
+
raise ValueError("If orders is a string, it must be 'all' to run ACID on all orders. Got: {orders!r}")
|
|
861
|
+
elif orders is None:
|
|
862
|
+
orders = self.orders
|
|
863
|
+
elif isinstance(orders, list):
|
|
864
|
+
if not all(isinstance(o, int) for o in orders):
|
|
865
|
+
raise ValueError("If orders is a list, all elements must be integers. Got: {orders!r}")
|
|
866
|
+
if not all(o in self.orders for o in orders):
|
|
867
|
+
raise ValueError("All orders in the input list must be in the DataList. Got: {orders!r}, but available orders are: {self.orders!r}")
|
|
868
|
+
else:
|
|
869
|
+
raise ValueError("orders must be an int, a list of ints, 'all', or None. Got: {orders!r}")
|
|
870
|
+
|
|
871
|
+
for order in orders:
|
|
872
|
+
result = Acid(data=self.data_list[self.o2i[order]]).ACID()
|
|
873
|
+
if self.save_dir is not None:
|
|
874
|
+
results_dir = os.path.join(self.save_dir, "results")
|
|
875
|
+
os.makedirs(results_dir, exist_ok=True)
|
|
876
|
+
save_path = os.path.join(results_dir, f"order_{order}.pkl")
|
|
877
|
+
result.save(save_path, store_sampler=store_sampler)
|
|
878
|
+
return
|
|
879
|
+
|
|
880
|
+
@classmethod
|
|
881
|
+
def create(
|
|
882
|
+
cls,
|
|
883
|
+
wavelengths : Array2D = None,
|
|
884
|
+
flux : Array2D = None,
|
|
885
|
+
errors : Array2D = None,
|
|
886
|
+
sn : Array1D = None,
|
|
887
|
+
order_range : Array1D|None = None,
|
|
888
|
+
load = None,
|
|
889
|
+
config : Config|None = None,
|
|
890
|
+
linelist : Array2D|None|str|LineList|dict = None,
|
|
891
|
+
velocities : Array1D|None = None,
|
|
892
|
+
save_dir : str|None = None,
|
|
893
|
+
verbose : IntLike|bool|None = None,
|
|
894
|
+
) -> DataList:
|
|
895
|
+
|
|
896
|
+
if load is not None:
|
|
897
|
+
from .load import Load
|
|
898
|
+
if not isinstance(load, Load):
|
|
899
|
+
raise ValueError("load must be an instance of the Load class. Got: {load!r}")
|
|
900
|
+
wavelengths, flux, errors, sn = load.get_data()
|
|
901
|
+
order_range = load.order_range
|
|
902
|
+
|
|
903
|
+
if order_range is None:
|
|
904
|
+
order_range = np.arange(len(wavelengths))
|
|
905
|
+
|
|
906
|
+
if len(order_range) != len(wavelengths):
|
|
907
|
+
raise ValueError("The length of the order_range must match the number of frames in the input data.")
|
|
908
|
+
|
|
909
|
+
config_dict = config.to_dict() if config is not None else {}
|
|
910
|
+
|
|
911
|
+
datalist = []
|
|
912
|
+
for order in order_range:
|
|
913
|
+
data = Data()
|
|
914
|
+
data.set_inputs(wavelengths[order], flux[order], errors[order], sn[order])
|
|
915
|
+
data.set_linelist(linelist=linelist)
|
|
916
|
+
data.velocities = velocities
|
|
917
|
+
config_dict["order"] = order
|
|
918
|
+
data.config = Config(**config_dict)
|
|
919
|
+
datalist.append(data)
|
|
920
|
+
|
|
921
|
+
return cls(datalist, save_dir=save_dir, verbose=verbose)
|
|
922
|
+
|
|
841
923
|
def save(self, save_dir:str|None=None):
|
|
842
924
|
d = {}
|
|
843
925
|
self.save_dir = save_dir
|
|
@@ -864,9 +946,8 @@ class DataList:
|
|
|
864
946
|
@save_dir.setter
|
|
865
947
|
def save_dir(self, dir):
|
|
866
948
|
if dir is None:
|
|
867
|
-
return
|
|
868
|
-
|
|
869
|
-
raise ValueError(f"save_dir must be a valid path to a directory to save the DataList, or None to not save to disk. Got: {dir}")
|
|
949
|
+
return None
|
|
950
|
+
os.mkdir(dir, exist_ok=True)
|
|
870
951
|
self._save_dir = dir
|
|
871
952
|
|
|
872
953
|
class LineList:
|
ACID_code/load.py
CHANGED
|
@@ -4,7 +4,8 @@ Each function will load a data object that can be directly input into the ACID i
|
|
|
4
4
|
"""
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
from beartype import beartype
|
|
7
|
-
|
|
7
|
+
import numpy as np
|
|
8
|
+
from .data import Data, DataList
|
|
8
9
|
from . import utils
|
|
9
10
|
from .utils import Array2D
|
|
10
11
|
from astropy.io import fits
|
|
@@ -33,9 +34,8 @@ class Load:
|
|
|
33
34
|
blaze_profile : Array2D, optional
|
|
34
35
|
Instead load the blaze profile yourself and input it here. This will override the blaze_file input if
|
|
35
36
|
both are provided. By default, None
|
|
36
|
-
|
|
37
|
-
|
|
38
37
|
"""
|
|
38
|
+
raise NotImplementedError("This class is still in development and not yet ready for use. Please check back later.")
|
|
39
39
|
|
|
40
40
|
self.file = file
|
|
41
41
|
self.blaze_file = blaze_file
|
|
@@ -86,18 +86,46 @@ class Load:
|
|
|
86
86
|
elif blaze_file is not None:
|
|
87
87
|
pass # TODO: load blaze file and save to self.data.blaze or tailor blaze files to each instrument
|
|
88
88
|
|
|
89
|
+
def to_datalist(self, **kwargs) -> DataList:
|
|
90
|
+
datalist = DataList.create(
|
|
91
|
+
wavelengths = self.wavelengths,
|
|
92
|
+
flux = self.flux,
|
|
93
|
+
errors = self.errors,
|
|
94
|
+
sn = self.sn,
|
|
95
|
+
order_range = self.order_range,
|
|
96
|
+
**kwargs,
|
|
97
|
+
)
|
|
98
|
+
return datalist
|
|
99
|
+
|
|
89
100
|
def HARPS(self):
|
|
90
101
|
# La Silla HARPS spectra
|
|
91
102
|
try:
|
|
92
103
|
hdr = self.hdul[0].header
|
|
93
104
|
data = self.hdul[0].data
|
|
105
|
+
wavelengths = self.hdul[1].data
|
|
94
106
|
except Exception as e:
|
|
95
107
|
raise ValueError(self.load_exception + str(e))
|
|
96
|
-
|
|
97
|
-
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if self.blaze_profile is not None:
|
|
111
|
+
try:
|
|
112
|
+
blaze_hdu = fits.open(self.blaze_file)
|
|
113
|
+
blaze_profiles = np.array(blaze_hdu[0].data)
|
|
114
|
+
self.flux = data / blaze_profiles
|
|
115
|
+
except:
|
|
116
|
+
print("Error loading blaze profile. No blaze correction applied.")
|
|
117
|
+
|
|
118
|
+
return
|
|
98
119
|
|
|
99
120
|
def HARPS_N(self):
|
|
100
|
-
|
|
121
|
+
hdul = fits.open(self.file)
|
|
122
|
+
hdr = hdul[0].header
|
|
123
|
+
flux = hdul[1].data
|
|
124
|
+
errors = hdul[2].data
|
|
125
|
+
dq = hdul[3].data
|
|
126
|
+
wavelengths = hdul[5].data
|
|
127
|
+
sn = np.array([hdr[f"HIERARCH TNG QC ORDER{i+1} SNR"] for i in range(len(flux))])
|
|
128
|
+
|
|
101
129
|
pass
|
|
102
130
|
|
|
103
131
|
def GEMINI_GHOST(self):
|
ACID_code/lsd.py
CHANGED
|
@@ -89,9 +89,9 @@ class LSD:
|
|
|
89
89
|
# Clip linelist to wavelength range of spectrum
|
|
90
90
|
wavelengths_linelist, depths_linelist = utils.clip_wavelengths(wavelengths, wavelengths_linelist, depths_linelist)
|
|
91
91
|
if len(wavelengths_linelist) == 0:
|
|
92
|
-
raise ValueError(f"No lines in linelist are within the wavelength range of the observed spectrum. "\
|
|
93
|
-
f"
|
|
94
|
-
f"
|
|
92
|
+
raise ValueError(f"No lines in linelist are within the wavelength range of the observed spectrum. \n"\
|
|
93
|
+
f"You may have mismatched wavelengths units between linelist and spectrum or an empty linelist.\n"\
|
|
94
|
+
f"Please check your linelist and input spectrum.")
|
|
95
95
|
|
|
96
96
|
# Apply S/N cut (of 1/(3*SN)) to linelist
|
|
97
97
|
wavelengths_linelist, depths_linelist = self.sn_clip(wavelengths_linelist, depths_linelist, sn)
|
ACID_code/profiles.py
CHANGED
|
@@ -16,7 +16,7 @@ class Profiles:
|
|
|
16
16
|
velocities : Array1D = None,
|
|
17
17
|
flux : Array1D = None,
|
|
18
18
|
flux_err : Array1D = None,
|
|
19
|
-
data : Data = None
|
|
19
|
+
data : Data = None,
|
|
20
20
|
) -> None:
|
|
21
21
|
"""Initializes the Profiles class with velocity, flux, and optional flux error data.
|
|
22
22
|
|
|
@@ -34,7 +34,7 @@ class Profiles:
|
|
|
34
34
|
data : Data, optional
|
|
35
35
|
A data instance to draw velocities, flux and flux errors. Will raise an
|
|
36
36
|
exception if they do not exist within the class.
|
|
37
|
-
Must be provided if all three of the above inputs were not passed, by default None.
|
|
37
|
+
Must be provided if all three of the above inputs were not passed, by default None.
|
|
38
38
|
"""
|
|
39
39
|
|
|
40
40
|
if data is not None:
|
ACID_code/result.py
CHANGED
|
@@ -162,13 +162,11 @@ class Result:
|
|
|
162
162
|
# Obtain flattened samples
|
|
163
163
|
flat_samples = self.sampler.get_chain(discard=self.burnin, thin=self.thin, flat=True)
|
|
164
164
|
|
|
165
|
-
# Getting the final profile and continuum values
|
|
165
|
+
# Getting the final profile and continuum values
|
|
166
166
|
nvel = len(self.data.velocities) if self.config.deterministic_profile is False else 0
|
|
167
167
|
quartiles = np.percentile(flat_samples, [16, 50, 84], axis=0)
|
|
168
168
|
errors = np.diff(quartiles, axis=0)
|
|
169
169
|
errors = np.max(errors, axis=0) # why?
|
|
170
|
-
self.profile = quartiles[1, :nvel]
|
|
171
|
-
self.profile_err = errors[:nvel]
|
|
172
170
|
self.poly_cos = quartiles[1, nvel:]
|
|
173
171
|
self.poly_cos_err = errors[nvel:]
|
|
174
172
|
|
|
@@ -191,7 +189,6 @@ class Result:
|
|
|
191
189
|
n_samples, ncoeffs = coeffs.shape
|
|
192
190
|
npix = powers.shape[0]
|
|
193
191
|
matrix_size_gb = (2 * n_samples * npix + n_samples * ncoeffs + npix * ncoeffs) * 8 * 1e-9
|
|
194
|
-
|
|
195
192
|
# If memory exceeded, fallback to using 1000 random samples
|
|
196
193
|
if matrix_size_gb > m_available:
|
|
197
194
|
if self.config.verbose > 0:
|
|
@@ -204,12 +201,11 @@ class Result:
|
|
|
204
201
|
conts = (coeffs @ powers.T)
|
|
205
202
|
continuum_error = np.std(conts, axis=0)
|
|
206
203
|
|
|
207
|
-
self.profiles = np.zeros((len(self.data.flux["input"]), 2, len(self.data.velocities)))
|
|
208
|
-
|
|
209
204
|
# First get the combined profile, and then calculate each frame's profile if there are multiple frames.
|
|
210
205
|
# If there is one frame, then the combined_profile is the same as the single frame profile.
|
|
211
|
-
|
|
212
|
-
|
|
206
|
+
nframes = len(self.data.flux["input"])
|
|
207
|
+
self.profiles = np.zeros((nframes, 2, len(self.data.velocities)))
|
|
208
|
+
for counter in range(nframes+1):
|
|
213
209
|
if counter == 0:
|
|
214
210
|
flux = np.copy(self.data.flux["combined"])
|
|
215
211
|
error = np.copy(self.data.errors["combined"])
|
|
@@ -242,8 +238,8 @@ class Result:
|
|
|
242
238
|
flux /= mdl
|
|
243
239
|
|
|
244
240
|
# Check whether we can skip alpha by reusing the same alpha, only true if the wavelength grid is identical
|
|
245
|
-
condition = np.
|
|
246
|
-
alpha = self.data.alpha if condition
|
|
241
|
+
condition = np.array_equal(wavelengths, self.data.wavelengths["combined"])
|
|
242
|
+
alpha = self.data.alpha if condition else None
|
|
247
243
|
|
|
248
244
|
LSD_profiles = LSD(self.data)
|
|
249
245
|
LSD_profiles.run_LSD(wavelengths, flux, error, sn=sn, alpha=alpha)
|
|
@@ -543,7 +539,7 @@ class Result:
|
|
|
543
539
|
flux = self.data.flux["combined"]
|
|
544
540
|
|
|
545
541
|
a, b = utils.get_normalisation_coeffs(wavelengths)
|
|
546
|
-
profile = utils.flux_to_od(self.combined_profile[0])
|
|
542
|
+
profile = utils.flux_to_od(self.combined_profile[0])
|
|
547
543
|
|
|
548
544
|
# Get flat_samples which are the same samples used to calculate the final profile
|
|
549
545
|
flat_samples = self.sampler.get_chain(discard=self.burnin, thin=self.thin, flat=True)
|
|
@@ -557,7 +553,7 @@ class Result:
|
|
|
557
553
|
ax[0].plot(wavelengths, flux, color='black', linewidth=1, label='Observed Spectrum')
|
|
558
554
|
ax[0].plot(wavelengths, model_flux, color='C0', linewidth=1, label='Forward Model Fit')
|
|
559
555
|
ax[0].plot(wavelengths, continuum_model, color='C1', linewidth=1, label='Fitted Continuum', linestyle='--')
|
|
560
|
-
ax[1].plot(wavelengths,
|
|
556
|
+
ax[1].plot(wavelengths, model_flux - flux, color='C0', linewidth=1, label='Residuals')
|
|
561
557
|
ax[1].axhline(0, color='black', linestyle='--', linewidth=1)
|
|
562
558
|
ax[0].set_title(labels["title"])
|
|
563
559
|
ax[1].set_xlabel(labels["xlabel"])
|
ACID_code/utils.py
CHANGED
|
@@ -354,7 +354,7 @@ def flux_to_od(flux=None, errors=None, linelist=None):
|
|
|
354
354
|
if linelist is not None:
|
|
355
355
|
out.append(-np.log(1 - linelist))
|
|
356
356
|
|
|
357
|
-
return tuple(out)
|
|
357
|
+
return tuple(out) if len(out) > 1 else out[0]
|
|
358
358
|
|
|
359
359
|
def od_to_flux(od=None, errors=None, linelist=None):
|
|
360
360
|
"""Converts optical depth to flux, errors, and linelist.
|
|
@@ -389,7 +389,7 @@ def od_to_flux(od=None, errors=None, linelist=None):
|
|
|
389
389
|
if linelist is not None:
|
|
390
390
|
out.append(1-np.exp(-linelist))
|
|
391
391
|
|
|
392
|
-
return tuple(out)
|
|
392
|
+
return tuple(out) if len(out) > 1 else out[0]
|
|
393
393
|
|
|
394
394
|
def configure_mp_environ(os):
|
|
395
395
|
"""Configures the multiprocessing environment variables for optimal performance.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
ACID_code/__init__.py,sha256=5ocFY2Pyeo4VclLFRlph8LM8tdIoPtwUmrQwgUnbVIk,463
|
|
2
|
+
ACID_code/acid.py,sha256=7BslKV9iGt6CXAdGTp2wrxes7cw592WHmff_ArweGt0,69763
|
|
3
|
+
ACID_code/data.py,sha256=5Oms-M5Jhi-0OT1mceBeVW9Pmrf0IWvBpMm-iZjV4po,50722
|
|
4
|
+
ACID_code/load.py,sha256=3gzIZpAv7flgX5ekWdRDI95hkPTxvtp2F1liygpivOQ,5453
|
|
5
|
+
ACID_code/lsd.py,sha256=YcShX62fqYCy2ZmFDOXEzKl-5nUM5QvYmCsk_KO8F9E,30495
|
|
6
|
+
ACID_code/mcmc.py,sha256=tsWZrEy8IZt-Hh3GJka3sIU8SkM4GivSgKyo8lWoycA,10564
|
|
7
|
+
ACID_code/profiles.py,sha256=Nm77ERkDByWKD5DeQv8VspV9eP98vqw-IyRYer-9qm8,13455
|
|
8
|
+
ACID_code/result.py,sha256=XTkLjTO6Wmu5MZeAl-1z80jyAIiXVaXnPC0_MDRqNrA,40303
|
|
9
|
+
ACID_code/utils.py,sha256=XF3Wi_t4bkBGYKrXeMipBNrs3Wp6R_YA3w9Qu-t1DCs,16348
|
|
10
|
+
acid_code-1.5.0a4.dist-info/licenses/LICENSE,sha256=L6dUgqjvHmRoobrBCPSHKC4UtRM5Ldp1DJBC4bnLk3w,1070
|
|
11
|
+
acid_code-1.5.0a4.dist-info/METADATA,sha256=hkq1Gwp55q4EciL0n5ugIkZkVB2fflEjOt8oNTenHrI,2979
|
|
12
|
+
acid_code-1.5.0a4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
13
|
+
acid_code-1.5.0a4.dist-info/top_level.txt,sha256=O4OaSabv1ebFYQmHgftr1PGAv6BvC2l81Y3HjgNehQI,10
|
|
14
|
+
acid_code-1.5.0a4.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
ACID_code/__init__.py,sha256=5ocFY2Pyeo4VclLFRlph8LM8tdIoPtwUmrQwgUnbVIk,463
|
|
2
|
-
ACID_code/acid.py,sha256=7BslKV9iGt6CXAdGTp2wrxes7cw592WHmff_ArweGt0,69763
|
|
3
|
-
ACID_code/data.py,sha256=AKLEhB5b3E__pgBmdLtQjzvKqDnYBIKmbeUsrnvk-Co,47133
|
|
4
|
-
ACID_code/load.py,sha256=a-9mFRsT8UDU-A5m-fPwDtPOS9ibPz8GnDdZaoiBGbo,4372
|
|
5
|
-
ACID_code/lsd.py,sha256=XBv3PuT5of5QW5EKon7wK_wuy0b8byjLbyXh_xvH2qM,30492
|
|
6
|
-
ACID_code/mcmc.py,sha256=tsWZrEy8IZt-Hh3GJka3sIU8SkM4GivSgKyo8lWoycA,10564
|
|
7
|
-
ACID_code/profiles.py,sha256=Z2Ttnk49-Aq5oR--qdmbv3R4EM7TjtUJ9YXGy8S1024,13453
|
|
8
|
-
ACID_code/result.py,sha256=_MY8Brr5z8eXhzcR46wMmE8BHHAIi0ErpmAvyy0U9lE,40424
|
|
9
|
-
ACID_code/utils.py,sha256=Y3RdQR-rrTsaUpABDV0eRhwMvubcLD1w18ubGziKt0g,16292
|
|
10
|
-
acid_code-1.5.0a3.dist-info/licenses/LICENSE,sha256=L6dUgqjvHmRoobrBCPSHKC4UtRM5Ldp1DJBC4bnLk3w,1070
|
|
11
|
-
acid_code-1.5.0a3.dist-info/METADATA,sha256=w5ukCW5ClgyTYXSumEs65_9p85rY1mw3pL5L4ydgQaA,2979
|
|
12
|
-
acid_code-1.5.0a3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
13
|
-
acid_code-1.5.0a3.dist-info/top_level.txt,sha256=O4OaSabv1ebFYQmHgftr1PGAv6BvC2l81Y3HjgNehQI,10
|
|
14
|
-
acid_code-1.5.0a3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|