jaxspec 0.0.2__py3-none-any.whl → 0.0.4__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.
@@ -343,7 +343,7 @@ class ChainResult:
343
343
  folding_model.out_energies,
344
344
  y_observed=folding_model.folded_background.data,
345
345
  y_samples=bkg_count,
346
- denominator=denominator / folding_model.backratio.data,
346
+ denominator=denominator * folding_model.folded_backratio.data,
347
347
  color=(0.26787604, 0.60085972, 0.63302651),
348
348
  percentile=percentile,
349
349
  )
jaxspec/data/__init__.py CHANGED
@@ -5,3 +5,6 @@ from .observation import Observation # noqa: F401
5
5
  import astropy.units as u
6
6
 
7
7
  u.add_enabled_aliases({"counts": u.count})
8
+ u.add_enabled_aliases({"channel": u.dimensionless_unscaled})
9
+ # Arbitrary units are found in .rsp files , let's hope it is compatible with what we would expect as the rmf x arf
10
+ # u.add_enabled_aliases({"au": u.dimensionless_unscaled})
@@ -0,0 +1,334 @@
1
+ SIMPLE = T / file does conform to FITS standard BITPIX = 8 / number of bits per data pixel NAXIS = 0 / number of data axes EXTEND = T / FITS dataset may contain extensions COMMENT FITS (Flexible Image Transport System) format is defined in 'AstronomyCOMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H END XTENSION= 'BINTABLE' / binary table extension BITPIX = 8 / 8-bit bytes NAXIS = 2 / 2-dimensional binary table NAXIS1 = 12 / width of table in bytes NAXIS2 = 4096 / number of rows in table PCOUNT = 0 / size of special data area GCOUNT = 1 / one data group (required keyword) TFIELDS = 4 / number of fields in each row TTYPE1 = 'CHANNEL ' / label for field 1 TFORM1 = 'J ' / data format of field: 4-byte INTEGER TTYPE2 = 'COUNTS ' / label for field 2 TFORM2 = 'J ' / data format of field: 4-byte INTEGER TTYPE3 = 'QUALITY ' / label for field 3 TFORM3 = 'I ' / data format of field: 2-byte INTEGER TTYPE4 = 'GROUPING' / label for field 4 TFORM4 = 'I ' / data format of field: 2-byte INTEGER EXTNAME = 'SPECTRUM' / name of this binary table extension HDUCLASS= 'OGIP ' / format conforms to OGIP standard HDUCLAS1= 'SPECTRUM' / PHA dataset (OGIP memo OGIP-92-007) HDUVERS = '1.2.1 ' / Version of format (OGIP memo OGIP-92-007a) TELESCOP= 'XMM ' / mission/satellite name INSTRUME= 'EPN ' / instrument name DETNAM = 'UNKNOWN ' / detector name FILTER = 'Medium ' / filter name STOKESPR= 'UNKNOWN ' / Stokes parameter(s) STOKESWT= 'UNKNOWN ' / Stokes weighting CHANTYPE= 'PI ' / channel type (PHA, PI etc) DATE = '2024-03-20T11:51:55' / file creation date (YYYY-MM-DDThh:mm:ss UT) HISTORY Fake data file created by XSPEC version: 12.13.1 "fakeit" command FKSRC001= 'cutoffpl' FKRSP001= 'PN.rmf ' FKARF001= 'PN.arf ' RESPFILE= 'PN.rmf ' / associated redistrib matrix filename ANCRFILE= 'PN.arf ' / associated ancillary response filename CORRFILE= ' ' / associated correction filename CORRSCAL= 1. / correction file scaling factor BACKFILE= ' ' / associated background filename EXPOSURE= 10000. / exposure (in seconds) TLMIN1 = 0 / Lowest legal channel number TLMAX1 = 4095 / Highest legal channel number DETCHANS= 4096 / total number possible channels POISSERR= T / Pois. err assumed ? AREASCAL= 1. / area scaling factor BACKSCAL= 1. / background file scaling factor END �!�x��� ��a��$8 0�
2
+ ;{ D- L�
3
+ �����������8������<��H�����������~�������#��|�����n�����������������/�������X��"��_��i��-�� �����Z��������������������N�����q��<�����q��������������_��������������������A�����������a��������Y��2�����n�����d��������!���?�_�` ��
4
+ �� �+ �+
5
+ &�'��(��)��*�+�<,�-�Q.��/��0��1�02�3�4�^5��6�[7�8�(9�a:�H;~�<�=~>}??|c@|A|8B{�C{mDy�E{�F{HGyHymIy�JxtKy�Ly Mx�Nw�Ow�PxRQw�RxSvTv�UvtVu�WuXXv4Yu�ZuY[u�\u�]t�^t�_t,`s�ar�bqNcr�drBeq�fp�gpxhn�im�jm�kk�ll�mk�ni
6
+ oipg�qgUre�sd�tc�ub�v`�wa�x`�y`Oz^�{]�|]�}] ~]_\��[��]�\(�[��[F�Z��[�Z��Zn�ZH�Y��Y��X��YK�Y.�W��X�V��W��X�WN�X��W`�U��U��U��V�V&�U�T��T��T��Ty�T0�S�S�Sl�R'�R*�R��QZ�Q3�QA�P}�O��O��N��NR�L��L��K��K}�JN�I��H+�G,�F=�EI�E��D��C)�B,�Ap�@��?Z�=��=��<L�:��:X�9(�9 �7t�7��5�6��4��4#�3��3b�2{�2��2$�1��1q�0�/��/��0M�/��/|�0n�/��/��/��/5�/��/�.��/x�0,�0 �/�/��.��/��/D�.��/5�.��0�.��0,�/I�/e�/�/2�/d�/
7
+ .4 -� -�
8
+ � ��!
9
+ �!� �� �� �� �� ���� 1� �� >� x� � � u� �� 9� A� *� /��� �� �T� � �����_�v���i�q���n�v���'���:�'��������&��}�lm����Z �
10
+ . d �
11
+ I � �
12
+ !  
13
+ �q 3r
14
+ �s t u
15
+ �v w
16
+ �x $y z
17
+ �{
18
+ �|
19
+ �} (~
20
+ o
21
+ ��
22
+ f�
23
+ ��
24
+ ��
25
+ ��
26
+ �� �
27
+ ��
28
+ ��
29
+ Y�
30
+ ��
31
+ x�
32
+ t�
33
+ ,�
34
+ i�
35
+ H�
36
+ +�
37
+ ��
38
+ b�
39
+ u�
40
+ �
41
+ n�
42
+ *�
43
+ � ��
44
+ h�
45
+ � ��
46
+ �
47
+ �
48
+ � �� �� �� �� �� �� �� ��
49
+ "� �� W� �� �� �� �� `� l� 1� o� N� x� g� � I� i� C� 5� � �� =� <� j� %� i��� D� M� 1� <��� F������� L��������� ��� �[�x�����U���v���������=���p�I�]�0����:�}�������D���D�(� ���E����p�������"�����|���������������}� �
50
+ � D �
51
+ 0�1�2�3�4 5�6�7�8�9�:�;�<�=�>S?~@�A�B�C�DpE]FLGVHvI"JsK�L�MZN�O�P.Q_R'SST�UxVWX�YWZ�[�\R]�^�_R`9ab!c d�e�f�g�h�i�j�k:lm�n�o�p�q�r�s�t�u�v�w�x�y�z�{�|�}�~�����������~���g�������F�I�^�p�i�V�R�\�s�U�*�E�v�1�p� �=���E�����=���#����������8�'�.�M�������������������������� �����������������������������h�������������������o�u�r�p���S�w�9�j�G�i��������!��R����T��Z�7�>�-�i� �5�����+�
52
+ ��S���.�����;�360�� �
53
+ �  �
54
+ � � �
55
+ � � �
56
+ *
57
+ +
58
+ -
59
+ +
60
+ &
61
+ 3
62
+ '
63
+ &
64
+ 1
65
+ -
66
+
67
+ 1
68
+ &
69
+ -
70
+
71
+ !
72
+  
73
+ *
74
+ 
75
+ *
76
+ 
77
+ ,
78
+ '
79
+ ,
80
+ !
81
+ "
82
+ 1
83
+ (
84
+ )
85
+ *
86
+ %
87
+ '
88
+ /
89
+ 
90
+ !,
91
+ ",
92
+ #0
93
+ $)
94
+ %#
95
+ &
96
+ '
97
+ (,
98
+ )
99
+ *
100
+ +,
101
+ ,
102
+ -'
103
+ .
104
+ /!
105
+ 0
106
+ 1&
107
+ 2 
108
+ 3
109
+ 4%
110
+ 5
111
+ 6#
112
+ 7
113
+ 8
114
+ 9&
115
+ :
116
+ ;
117
+ <
118
+ =
119
+ >%
120
+ ?/
121
+ @
122
+ A"
123
+ B'
124
+ C'
125
+ D%
126
+ E
127
+ F
128
+ G
129
+ H(
130
+ I
131
+ J#
132
+ K
133
+ L
134
+ M-
135
+ N
136
+ O#
137
+ P
138
+ Q
139
+ R
140
+ S!
141
+ T
142
+ U
143
+ V%
144
+ W
145
+ X"
146
+ Y
147
+ Z
148
+ [
149
+ \
150
+ ]
151
+ ^
152
+ _
153
+ `
154
+ a 
155
+ b
156
+ c
157
+ d
158
+ e
159
+ f
160
+ g
161
+ h
162
+ i
163
+ j
164
+ k
165
+ l
166
+ m
167
+ n
168
+ o
169
+ p 
170
+ q
171
+ r
172
+ s
173
+ t
174
+ u
175
+ v
176
+ w
177
+ x
178
+ y
179
+ z
180
+ {
181
+ |
182
+ }
183
+ ~
184
+ 
185
+ �
186
+ �
187
+ �
188
+ �
189
+ �
190
+ �
191
+ �
192
+ �
193
+ �
194
+ �
195
+ �
196
+ �
197
+ �
198
+ �
199
+ �
200
+ �
201
+ �
202
+ �
203
+ �
204
+ �
205
+ �
206
+
207
+ �
208
+ �
209
+ �
210
+
211
+ �
212
+ �
213
+ �
214
+ �
215
+ �
216
+
217
+ �
218
+
219
+ �
220
+ �
221
+ � 
222
+ �
223
+ � 
224
+ �
225
+ �
226
+ �
227
+ �
228
+
229
+ �
230
+ �
231
+ �
232
+
233
+ �
234
+
235
+ 
236
+ � 
237
+ �
238
+ � 
239
+ �
240
+ �
241
+ � 
242
+ �
243
+ �
244
+ � 
245
+ � 
246
+
247
+ �
248
+ �
249
+ �
250
+ � 
251
+ �
252
+ �
253
+ �
254
+ �
255
+ �
256
+
257
+ 
258
+ �
259
+ � 
260
+ � 
261
+ � 
262
+ �
263
+ �
264
+ � 
265
+
266
+ � 
267
+ � 
268
+ � 
269
+ �
270
+
271
+
272
+ �
273
+ �
274
+ �
275
+ �
276
+ � 
277
+ �
278
+
279
+ 
280
+ �
281
+ � 
282
+ �
283
+ �
284
+ �
285
+ �
286
+
287
+ � 
288
+ � 
289
+
290
+
291
+ 
292
+ �
293
+ �
294
+ � 
295
+ �
296
+ � 
297
+ � 
298
+ �
299
+
300
+ � 
301
+
302
+ 
303
+ � 
304
+ �
305
+ �
306
+ � 
307
+ �
308
+ �
309
+ �
310
+ �
311
+ �
312
+ �
313
+ �
314
+ � 
315
+ � 
316
+ �
317
+ �           
318
+  
319
+  
320
+ 
321
+                
322
+    
323
+   
324
+   !
325
+  +  ,  - . /  0 1 2 3 4  5  6 7 8 9 : ; < = > ? @ A B C D
326
+  E F G  H
327
+  I J K L M N  O P Q  R
328
+  S T U V W X Y
329
+  Z [ \ ] ^ _ ` a b  c d e f g h i j k l m n o  p q r s t u
330
+  v w x y z { | } ~  � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �  � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �          
331
+   
332
+ 
333
  
334
+ 
1
- 
335
+   
336
+   
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import numpy as np
3
3
  import xarray as xr
4
+ from matplotlib import colors
4
5
  from .ogip import DataARF, DataRMF
5
6
 
6
7
 
@@ -64,21 +65,26 @@ class Instrument(xr.Dataset):
64
65
  )
65
66
 
66
67
  @classmethod
67
- def from_ogip_file(cls, arf_file: str | os.PathLike, rmf_file: str | os.PathLike, **kwargs):
68
+ def from_ogip_file(cls, rmf_path: str | os.PathLike, arf_path: str | os.PathLike = None):
68
69
  """
69
70
  Load the data from OGIP files.
70
71
 
71
72
  Parameters:
72
- arf_file: The ARF file path.
73
- rmf_file: The RMF file path.
73
+ rmf_path: The RMF file path.
74
+ arf_path: The ARF file path.
74
75
  exposure: The exposure time in second.
75
76
  grouping: The grouping matrix.
76
77
  """
77
78
 
78
- arf = DataARF.from_file(arf_file)
79
- rmf = DataRMF.from_file(rmf_file)
79
+ rmf = DataRMF.from_file(rmf_path)
80
80
 
81
- return cls.from_matrix(rmf.matrix, arf.specresp, rmf.energ_lo, rmf.energ_hi, rmf.e_min, rmf.e_max)
81
+ if arf_path is not None:
82
+ specresp = DataARF.from_file(arf_path).specresp
83
+
84
+ else:
85
+ specresp = np.ones(rmf.energ_lo.shape)
86
+
87
+ return cls.from_matrix(rmf.sparse_matrix, specresp, rmf.energ_lo, rmf.energ_hi, rmf.e_min, rmf.e_max)
82
88
 
83
89
  def plot_redistribution(self, **kwargs):
84
90
  """
@@ -95,9 +101,8 @@ class Instrument(xr.Dataset):
95
101
  y="e_max_channel",
96
102
  xscale="log",
97
103
  yscale="log",
98
- cmap=cmr.cosmic,
99
- vmin=0,
100
- vmax=0.075,
104
+ cmap=cmr.ember_r,
105
+ norm=colors.LogNorm(vmin=1e-6, vmax=1),
101
106
  add_labels=True,
102
107
  **kwargs,
103
108
  )
jaxspec/data/obsconf.py CHANGED
@@ -1,5 +1,7 @@
1
1
  import numpy as np
2
2
  import xarray as xr
3
+ import sparse
4
+ import scipy
3
5
  from .instrument import Instrument
4
6
  from .observation import Observation
5
7
 
@@ -51,31 +53,27 @@ class ObsConfiguration(xr.Dataset):
51
53
 
52
54
  out_energies = np.stack(
53
55
  (
54
- np.asarray(self.coords["e_min_folded"], dtype=np.float64),
55
- np.asarray(self.coords["e_max_folded"], dtype=np.float64),
56
+ np.asarray(self.coords["e_min_folded"].data, dtype=np.float64),
57
+ np.asarray(self.coords["e_max_folded"].data, dtype=np.float64),
56
58
  )
57
59
  )
58
60
 
59
61
  return out_energies
60
62
 
61
63
  @classmethod
62
- def from_pha_file(cls, pha_file, low_energy: float = 1e-20, high_energy: float = 1e20):
63
- from .util import data_loader
64
-
65
- pha, arf, rmf, bkg, metadata = data_loader(pha_file)
66
-
67
- instrument = Instrument.from_matrix(rmf.matrix, arf.specresp, rmf.energ_lo, rmf.energ_hi, rmf.e_min, rmf.e_max)
68
-
69
- observation = Observation.from_matrix(
70
- pha.counts,
71
- pha.grouping,
72
- pha.channel,
73
- pha.quality,
74
- pha.exposure,
75
- background=bkg.counts if bkg is not None else None,
76
- backratio=pha.backscal / bkg.backscal if bkg is not None else 1.0,
77
- attributes=metadata,
78
- )
64
+ def from_pha_file(
65
+ cls, pha_path, rmf_path=None, arf_path=None, bkg_path=None, low_energy: float = 1e-20, high_energy: float = 1e20
66
+ ):
67
+ from .util import data_path_finder
68
+
69
+ arf_path_default, rmf_path_default, bkg_path_default = data_path_finder(pha_path)
70
+
71
+ arf_path = arf_path_default if arf_path is None else arf_path
72
+ rmf_path = rmf_path_default if rmf_path is None else rmf_path
73
+ bkg_path = bkg_path_default if bkg_path is None else bkg_path
74
+
75
+ instrument = Instrument.from_ogip_file(rmf_path, arf_path=arf_path)
76
+ observation = Observation.from_pha_file(pha_path, bkg_path=bkg_path)
79
77
 
80
78
  return cls.from_instrument(instrument, observation, low_energy=low_energy, high_energy=high_energy)
81
79
 
@@ -83,48 +81,108 @@ class ObsConfiguration(xr.Dataset):
83
81
  def from_instrument(
84
82
  cls, instrument: Instrument, observation: Observation, low_energy: float = 1e-20, high_energy: float = 1e20
85
83
  ):
86
- grouping = observation.grouping.copy()
84
+ # First we unpack all the xarray data to classical np array for efficiency
85
+ # We also exclude the bins that are flagged with bad quality on the instrument
86
+ quality_filter = observation.quality.data == 0
87
+ grouping = scipy.sparse.csr_array(observation.grouping.data.to_scipy_sparse()) * quality_filter
88
+ e_min_channel = instrument.coords["e_min_channel"].data
89
+ e_max_channel = instrument.coords["e_max_channel"].data
90
+ e_min_unfolded = instrument.coords["e_min_unfolded"].data
91
+ e_max_unfolded = instrument.coords["e_max_unfolded"].data
92
+ redistribution = scipy.sparse.csr_array(instrument.redistribution.data.to_scipy_sparse())
93
+ area = instrument.area.data
94
+ exposure = observation.exposure.data
87
95
 
88
96
  # Computing the lower and upper energies of the bins after grouping
89
97
  # This is just a trick to compute it without 10 lines of code
90
- e_min = (xr.where(grouping > 0, grouping, np.nan) * instrument.coords["e_min_channel"]).min(
91
- skipna=True, dim="instrument_channel"
92
- )
93
- e_max = (xr.where(grouping > 0, grouping, np.nan) * instrument.coords["e_max_channel"]).max(
94
- skipna=True, dim="instrument_channel"
95
- )
98
+ grouping_nan = observation.grouping.data * quality_filter
99
+ grouping_nan.fill_value = np.nan
100
+ e_min = sparse.nanmin(grouping_nan * e_min_channel, axis=1).todense()
101
+ e_max = sparse.nanmax(grouping_nan * e_max_channel, axis=1).todense()
96
102
 
97
- transfer_matrix = grouping @ (instrument.redistribution * instrument.area * observation.exposure)
98
- transfer_matrix = transfer_matrix.assign_coords({"e_min_folded": e_min, "e_max_folded": e_max})
103
+ # Compute the transfer matrix
104
+ transfer_matrix = grouping @ (redistribution * area * exposure)
99
105
 
100
- # Exclude the bins flagged with bad quality
101
- quality_filter = observation.quality == 0
102
- grouping[:, ~quality_filter] = 0
106
+ # Exclude bins out of the considered energy range, and bins without contribution from the RMF
103
107
 
104
- row_idx = xr.ones_like(e_min, dtype=bool)
105
- row_idx *= (e_min > low_energy) & (e_max < high_energy) # Strict exclusion as in XSPEC
106
- row_idx *= grouping.sum(dim="instrument_channel") > 0 # Exclude channels with no contribution
108
+ row_idx = (e_min > low_energy) & (e_max < high_energy) & (grouping.sum(axis=1) > 0)
109
+ col_idx = (e_min_unfolded > 0) & (redistribution.sum(axis=0) > 0)
107
110
 
108
- col_idx = xr.ones_like(instrument.area, dtype=bool)
109
- col_idx *= instrument.coords["e_min_unfolded"] > 0.0 # Exclude channels with 0. as lower energy bound
110
- col_idx *= instrument.redistribution.sum(dim="instrument_channel") > 0 # Exclude channels with no contribution
111
-
112
- transfer_matrix = transfer_matrix.where(row_idx & col_idx, drop=True)
113
- folded_counts = observation.folded_counts.copy().where(row_idx, drop=True)
111
+ # Apply this reduction to all the relevant arrays
112
+ transfer_matrix = sparse.COO.from_scipy_sparse(transfer_matrix[row_idx][:, col_idx])
113
+ folded_counts = observation.folded_counts.data[row_idx]
114
+ folded_backratio = observation.folded_backratio.data[row_idx]
115
+ area = instrument.area.data[col_idx]
116
+ e_min_folded = e_min[row_idx]
117
+ e_max_folded = e_max[row_idx]
118
+ e_min_unfolded = e_min_unfolded[col_idx]
119
+ e_max_unfolded = e_max_unfolded[col_idx]
114
120
 
115
121
  if observation.folded_background is not None:
116
- folded_background = observation.folded_background.copy().where(row_idx, drop=True)
117
-
122
+ folded_background = observation.folded_background.data[row_idx]
118
123
  else:
119
- folded_background = None
124
+ folded_background = np.zeros_like(folded_counts)
125
+
126
+ data_dict = {
127
+ "transfer_matrix": (
128
+ ["folded_channel", "unfolded_channel"],
129
+ transfer_matrix,
130
+ {
131
+ "description": "Transfer matrix to use to fold the incoming spectrum. It is built and restricted using the grouping, redistribution matrix, effective area, quality flags and energy bands defined by the user."
132
+ },
133
+ ),
134
+ "area": (
135
+ ["unfolded_channel"],
136
+ area,
137
+ {"description": "Effective area with the same restrictions as the transfer matrix.", "units": "cm^2"},
138
+ ),
139
+ "exposure": ([], exposure, {"description": "Total exposure", "unit": "s"}),
140
+ "folded_counts": (
141
+ ["folded_channel"],
142
+ folded_counts,
143
+ {
144
+ "description": "Folded counts after grouping, with the same restrictions as the transfer matrix.",
145
+ "unit": "photons",
146
+ },
147
+ ),
148
+ "folded_backratio": (
149
+ ["folded_channel"],
150
+ folded_backratio,
151
+ {"description": "Background scaling after grouping, with the same restrictions as the transfer matrix."},
152
+ ),
153
+ "folded_background": (
154
+ ["folded_channel"],
155
+ folded_background,
156
+ {
157
+ "description": "Folded background counts after grouping, with the same restrictions as the transfer matrix.",
158
+ "unit": "photons",
159
+ },
160
+ ),
161
+ }
120
162
 
121
163
  return cls(
122
- {
123
- "transfer_matrix": transfer_matrix,
124
- "area": instrument.area.copy().where(col_idx, drop=True),
125
- "exposure": observation.exposure,
126
- "backratio": observation.backratio,
127
- "folded_counts": folded_counts,
128
- "folded_background": folded_background,
129
- }
164
+ data_dict,
165
+ coords={
166
+ "e_min_folded": (
167
+ ["folded_channel"],
168
+ e_min_folded,
169
+ {"description": "Low energy of folded channel"},
170
+ ),
171
+ "e_max_folded": (
172
+ ["folded_channel"],
173
+ e_max_folded,
174
+ {"description": "High energy of folded channel"},
175
+ ),
176
+ "e_min_unfolded": (
177
+ ["unfolded_channel"],
178
+ e_min_unfolded,
179
+ {"description": "Low energy of unfolded channel"},
180
+ ),
181
+ "e_max_unfolded": (
182
+ ["unfolded_channel"],
183
+ e_max_unfolded,
184
+ {"description": "High energy of unfolded channel"},
185
+ ),
186
+ },
187
+ attrs=observation.attrs | instrument.attrs,
130
188
  )