aspcap 0.2.0__tar.gz
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.
- aspcap-0.2.0/LICENSE +21 -0
- aspcap-0.2.0/LICENSE-DOPPLER +21 -0
- aspcap-0.2.0/MANIFEST.in +4 -0
- aspcap-0.2.0/PKG-INFO +185 -0
- aspcap-0.2.0/README.md +147 -0
- aspcap-0.2.0/aspcap/__init__.py +8 -0
- aspcap-0.2.0/aspcap/abundances.py +73 -0
- aspcap-0.2.0/aspcap/cli.py +64 -0
- aspcap-0.2.0/aspcap/continuum.py +437 -0
- aspcap-0.2.0/aspcap/data/apogee-dr17-mgiant-full-weights.json +1 -0
- aspcap-0.2.0/aspcap/data/apogee-dr17-mgiant-full.hdr +15 -0
- aspcap-0.2.0/aspcap/data/apogee-dr17-mgiant-full.unf +0 -0
- aspcap-0.2.0/aspcap/ferre.py +456 -0
- aspcap-0.2.0/aspcap/spectrum.py +551 -0
- aspcap-0.2.0/aspcap.egg-info/PKG-INFO +185 -0
- aspcap-0.2.0/aspcap.egg-info/SOURCES.txt +39 -0
- aspcap-0.2.0/aspcap.egg-info/dependency_links.txt +1 -0
- aspcap-0.2.0/aspcap.egg-info/entry_points.txt +2 -0
- aspcap-0.2.0/aspcap.egg-info/requires.txt +2 -0
- aspcap-0.2.0/aspcap.egg-info/top_level.txt +1 -0
- aspcap-0.2.0/docs/conf.py +9 -0
- aspcap-0.2.0/docs/continuum.rst +95 -0
- aspcap-0.2.0/docs/grid.rst +195 -0
- aspcap-0.2.0/docs/index.rst +26 -0
- aspcap-0.2.0/docs/inputs.rst +59 -0
- aspcap-0.2.0/docs/python-api.rst +57 -0
- aspcap-0.2.0/docs/quickstart.rst +46 -0
- aspcap-0.2.0/docs/requirements.txt +1 -0
- aspcap-0.2.0/docs/spectrum.rst +116 -0
- aspcap-0.2.0/examples/apogee-dr17-mgiant-mini.dat +42 -0
- aspcap-0.2.0/examples/download_dr17_mgiant.py +112 -0
- aspcap-0.2.0/examples/mg.json +6 -0
- aspcap-0.2.0/examples/one-star.json +8 -0
- aspcap-0.2.0/examples/spectrum_continuum.py +21 -0
- aspcap-0.2.0/examples/toy-grid.dat +28 -0
- aspcap-0.2.0/examples/toy-spectrum.csv +9 -0
- aspcap-0.2.0/pyproject.toml +27 -0
- aspcap-0.2.0/setup.cfg +4 -0
- aspcap-0.2.0/tests/test_abundances.py +92 -0
- aspcap-0.2.0/tests/test_continuum.py +186 -0
- aspcap-0.2.0/tests/test_spectrum.py +159 -0
aspcap-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David Nidever
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 David Nidever
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
aspcap-0.2.0/MANIFEST.in
ADDED
aspcap-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aspcap
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Element abundance fits with FERRE
|
|
5
|
+
Author: David Nidever
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 David Nidever
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Documentation, https://aspcap.readthedocs.io/
|
|
29
|
+
Project-URL: Repository, https://github.com/dnidever/aspcap
|
|
30
|
+
Project-URL: Issues, https://github.com/dnidever/aspcap/issues
|
|
31
|
+
Requires-Python: >=3.9
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
License-File: LICENSE
|
|
34
|
+
License-File: LICENSE-DOPPLER
|
|
35
|
+
Requires-Dist: numpy>=1.23
|
|
36
|
+
Requires-Dist: scipy>=1.9
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# aspcap
|
|
40
|
+
|
|
41
|
+
ASPCAP provides multi-order spectral data objects, external continuum
|
|
42
|
+
normalization, and element-abundance fitting with FERRE for HGE.
|
|
43
|
+
|
|
44
|
+
- [Spec1D objects](https://github.com/dnidever/aspcap/blob/main/docs/spectrum.rst): single or multiple orders, unequal chip
|
|
45
|
+
lengths, masks, uncertainties, copying, and native-grid mapping.
|
|
46
|
+
- [External continuum normalization](https://github.com/dnidever/aspcap/blob/main/docs/continuum.rst): polynomial, spline,
|
|
47
|
+
median, percentile, and model-ratio methods with diagnostics.
|
|
48
|
+
- [FERRE abundance fitting](https://github.com/dnidever/aspcap/blob/main/docs/quickstart.rst): fit an element using fixed
|
|
49
|
+
global parameters and a supplied continuum.
|
|
50
|
+
|
|
51
|
+
Install the latest release from PyPI with:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
python -m pip install --upgrade aspcap
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The spectrum and continuum modules do not need a FERRE executable or Doppler
|
|
58
|
+
model files. Install [FERRE](https://github.com/dnidever/ferre) separately when
|
|
59
|
+
running spectral fits: `python -m pip install --upgrade ferre`.
|
|
60
|
+
|
|
61
|
+
## Try the worked example
|
|
62
|
+
|
|
63
|
+
Install Python 3.9 or later and [FERRE](https://github.com/dnidever/ferre).
|
|
64
|
+
The example was verified with FERRE 5.1.3. From the repository checkout, run:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
python -m pip install -e .
|
|
68
|
+
aspcap abundances examples/one-star.json --output abundance.csv --pixels pixels.csv
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
If `ferre` is outside your PATH, add `--ferre /path/to/ferre`. The example
|
|
72
|
+
uses a synthetic spectrum and a toy grid. Expect a raw `ALPHA` coordinate of
|
|
73
|
+
about 0.20 and a reported `[Mg/H]` of about −0.05, using four pixels and fixed
|
|
74
|
+
`METALS = -0.25`. The pixel CSV shows the normalized flux, uncertainty, model,
|
|
75
|
+
and whether each pixel was used. Add `--keep-runs` to inspect FERRE's input
|
|
76
|
+
files after a successful fit. A failed fit keeps its files and reports their
|
|
77
|
+
location.
|
|
78
|
+
|
|
79
|
+
## Run your own fit
|
|
80
|
+
|
|
81
|
+
1. Copy [one-star.json](https://github.com/dnidever/aspcap/blob/main/examples/one-star.json) and point it to your inputs.
|
|
82
|
+
Relative paths are resolved from the configuration file. Supply the global
|
|
83
|
+
fit as a dictionary with one starting value per grid label.
|
|
84
|
+
2. Supply a spectrum CSV with `wavelength,flux,error,continuum` columns. The
|
|
85
|
+
spectrum must already be on the native rest-frame grid in the library's
|
|
86
|
+
wavelength units and air/vacuum convention. Give flux and one-sigma error
|
|
87
|
+
in the same units as the positive global continuum. The wrapper divides
|
|
88
|
+
both by that continuum and holds it fixed during the fit.
|
|
89
|
+
3. Copy [mg.json](https://github.com/dnidever/aspcap/blob/main/examples/mg.json) and set the species, the one free grid label,
|
|
90
|
+
and one or more inclusive wavelength windows. Overlapping windows still
|
|
91
|
+
give each pixel weight one. Nonfinite flux and nonpositive or nonfinite
|
|
92
|
+
uncertainties get zero weight. Use `add_labels` to list any fixed logarithmic
|
|
93
|
+
coordinates to add to the fitted coordinate to report `[X/H]`. Leave it empty
|
|
94
|
+
if the fitted coordinate is already `[X/H]`. Verify this mapping and the
|
|
95
|
+
wavelength windows against the scientific conventions of your grid.
|
|
96
|
+
|
|
97
|
+
This version supports single-segment, non-PCA FERRE libraries with a positive
|
|
98
|
+
spectral resolution, either formatted ASCII or matching `.hdr` and `.unf`
|
|
99
|
+
files. Point `grid` to the ASCII library or to the binary library's `.hdr`.
|
|
100
|
+
The fit uses linear interpolation. The reported error is conditional on the
|
|
101
|
+
fixed global parameters and continuum. The reduced χ² column comes from FERRE.
|
|
102
|
+
|
|
103
|
+
## Use from Python
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from aspcap import Grid, fit_abundance
|
|
107
|
+
|
|
108
|
+
grid = Grid.read("examples/toy-grid.dat")
|
|
109
|
+
result, pixels = fit_abundance(
|
|
110
|
+
grid, wavelength, flux, error, continuum,
|
|
111
|
+
{"ALPHA": 0.0, "METALS": -0.25},
|
|
112
|
+
star_id="my-star", species="Mg", label="ALPHA",
|
|
113
|
+
windows=[(15002, 15005)], add_labels=["METALS"],
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Pass the four CSV columns as sequences. `result` is a dictionary and `pixels`
|
|
118
|
+
iterates over the diagnostic rows.
|
|
119
|
+
|
|
120
|
+
The FERRE protocol lives in `aspcap/ferre.py`, the abundance logic in
|
|
121
|
+
`aspcap/abundances.py`, and file input and output in `aspcap/cli.py`. Run
|
|
122
|
+
`python -m unittest discover -s tests -v` for the tests. Set
|
|
123
|
+
`ASPCAP_TEST_FERRE=/path/to/ferre` to include the integration test if FERRE
|
|
124
|
+
is outside your PATH.
|
|
125
|
+
|
|
126
|
+
## Documentation
|
|
127
|
+
|
|
128
|
+
The [user guide](https://github.com/dnidever/aspcap/blob/main/docs/index.rst) includes a quickstart, input reference,
|
|
129
|
+
[Spec1D guide](https://github.com/dnidever/aspcap/blob/main/docs/spectrum.rst), [continuum methods](https://github.com/dnidever/aspcap/blob/main/docs/continuum.rst), and
|
|
130
|
+
Python API. The repository includes Read the Docs build configuration. To
|
|
131
|
+
preview the guide locally after installing ASPCAP:
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
python -m pip install -r docs/requirements.txt
|
|
135
|
+
python -m sphinx -W -b html docs docs/_build/html
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
The full-wavelength APOGEE DR17 M-giant test grid and detector weights are
|
|
140
|
+
included in ASPCAP installations.
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
python -m pip install --upgrade aspcap
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
See the [Grid walkthrough](https://github.com/dnidever/aspcap/blob/main/docs/grid.rst#fit-the-full-apogee-dr17-wavelength-grid)
|
|
147
|
+
for an example using the installed files.
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
## Multi-order spectra and external continuum fitting
|
|
151
|
+
|
|
152
|
+
`Spec1D` follows Doppler's `(pixels, orders)` layout, accepts unequal-length
|
|
153
|
+
orders, preserves masks/metadata, and can map detector chips back to a native
|
|
154
|
+
wavelength grid. It imports no Doppler fitting code or model loaders. Existing
|
|
155
|
+
Doppler objects can be used directly with the continuum functions.
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from aspcap import Spec1D, continuum
|
|
159
|
+
|
|
160
|
+
spec = Spec1D(flux_orders, wave=wavelength_orders, err=error_orders)
|
|
161
|
+
result = continuum.fit(spec, method="chebyshev", degree=4)
|
|
162
|
+
normalized = result.apply(spec) # independent copy
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Choose the estimator with `continuum.fit(spec, method=...)`:
|
|
166
|
+
|
|
167
|
+
| Method | Useful options | Defaults |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| `chebyshev` / `polynomial` | `degree`, `weighted`, `clip` | Degree 4, unweighted, no clipping |
|
|
170
|
+
| `spline` | `degree`, `smoothing`, `weighted`, `clip` | Degree 3, noise-based smoothing |
|
|
171
|
+
| `median` | `window`, `mode` | 151 pixels, mirror edges |
|
|
172
|
+
| `percentile` | `percentile`, `bins` | 90th percentile, 20 bins |
|
|
173
|
+
| `model_ratio` | `model`, `window`, `ratio_bounds`, `mode` | Model required; 151 pixels; ratios 0.1–10 |
|
|
174
|
+
|
|
175
|
+
Methods operate independently per order. Use `result.fit_mask`, `result.flags`,
|
|
176
|
+
`result.parameters`, and `result.settings` to inspect the estimate. Invalid
|
|
177
|
+
orders raise an error by default; `on_failure="mask"` returns flagged, masked
|
|
178
|
+
results instead. `result.apply(spec)` returns a copy; `inplace=True` updates the
|
|
179
|
+
input explicitly. All normalization here is outside FERRE. Applying a
|
|
180
|
+
continuum rescales both flux and error; it does not include uncertainty in the
|
|
181
|
+
continuum estimate. See [the spectrum guide](https://github.com/dnidever/aspcap/blob/main/docs/spectrum.rst),
|
|
182
|
+
[the continuum guide](https://github.com/dnidever/aspcap/blob/main/docs/continuum.rst), and the runnable
|
|
183
|
+
[example](https://github.com/dnidever/aspcap/blob/main/examples/spectrum_continuum.py). The core design is adapted from
|
|
184
|
+
[Doppler Spec1D](https://github.com/dnidever/doppler/blob/6e165f87c6d766eff6eaa1fbee2c82be5bb30cc0/doppler/spec1d.py);
|
|
185
|
+
its MIT notice is retained in `LICENSE-DOPPLER`.
|
aspcap-0.2.0/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# aspcap
|
|
2
|
+
|
|
3
|
+
ASPCAP provides multi-order spectral data objects, external continuum
|
|
4
|
+
normalization, and element-abundance fitting with FERRE for HGE.
|
|
5
|
+
|
|
6
|
+
- [Spec1D objects](https://github.com/dnidever/aspcap/blob/main/docs/spectrum.rst): single or multiple orders, unequal chip
|
|
7
|
+
lengths, masks, uncertainties, copying, and native-grid mapping.
|
|
8
|
+
- [External continuum normalization](https://github.com/dnidever/aspcap/blob/main/docs/continuum.rst): polynomial, spline,
|
|
9
|
+
median, percentile, and model-ratio methods with diagnostics.
|
|
10
|
+
- [FERRE abundance fitting](https://github.com/dnidever/aspcap/blob/main/docs/quickstart.rst): fit an element using fixed
|
|
11
|
+
global parameters and a supplied continuum.
|
|
12
|
+
|
|
13
|
+
Install the latest release from PyPI with:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
python -m pip install --upgrade aspcap
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The spectrum and continuum modules do not need a FERRE executable or Doppler
|
|
20
|
+
model files. Install [FERRE](https://github.com/dnidever/ferre) separately when
|
|
21
|
+
running spectral fits: `python -m pip install --upgrade ferre`.
|
|
22
|
+
|
|
23
|
+
## Try the worked example
|
|
24
|
+
|
|
25
|
+
Install Python 3.9 or later and [FERRE](https://github.com/dnidever/ferre).
|
|
26
|
+
The example was verified with FERRE 5.1.3. From the repository checkout, run:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
python -m pip install -e .
|
|
30
|
+
aspcap abundances examples/one-star.json --output abundance.csv --pixels pixels.csv
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If `ferre` is outside your PATH, add `--ferre /path/to/ferre`. The example
|
|
34
|
+
uses a synthetic spectrum and a toy grid. Expect a raw `ALPHA` coordinate of
|
|
35
|
+
about 0.20 and a reported `[Mg/H]` of about −0.05, using four pixels and fixed
|
|
36
|
+
`METALS = -0.25`. The pixel CSV shows the normalized flux, uncertainty, model,
|
|
37
|
+
and whether each pixel was used. Add `--keep-runs` to inspect FERRE's input
|
|
38
|
+
files after a successful fit. A failed fit keeps its files and reports their
|
|
39
|
+
location.
|
|
40
|
+
|
|
41
|
+
## Run your own fit
|
|
42
|
+
|
|
43
|
+
1. Copy [one-star.json](https://github.com/dnidever/aspcap/blob/main/examples/one-star.json) and point it to your inputs.
|
|
44
|
+
Relative paths are resolved from the configuration file. Supply the global
|
|
45
|
+
fit as a dictionary with one starting value per grid label.
|
|
46
|
+
2. Supply a spectrum CSV with `wavelength,flux,error,continuum` columns. The
|
|
47
|
+
spectrum must already be on the native rest-frame grid in the library's
|
|
48
|
+
wavelength units and air/vacuum convention. Give flux and one-sigma error
|
|
49
|
+
in the same units as the positive global continuum. The wrapper divides
|
|
50
|
+
both by that continuum and holds it fixed during the fit.
|
|
51
|
+
3. Copy [mg.json](https://github.com/dnidever/aspcap/blob/main/examples/mg.json) and set the species, the one free grid label,
|
|
52
|
+
and one or more inclusive wavelength windows. Overlapping windows still
|
|
53
|
+
give each pixel weight one. Nonfinite flux and nonpositive or nonfinite
|
|
54
|
+
uncertainties get zero weight. Use `add_labels` to list any fixed logarithmic
|
|
55
|
+
coordinates to add to the fitted coordinate to report `[X/H]`. Leave it empty
|
|
56
|
+
if the fitted coordinate is already `[X/H]`. Verify this mapping and the
|
|
57
|
+
wavelength windows against the scientific conventions of your grid.
|
|
58
|
+
|
|
59
|
+
This version supports single-segment, non-PCA FERRE libraries with a positive
|
|
60
|
+
spectral resolution, either formatted ASCII or matching `.hdr` and `.unf`
|
|
61
|
+
files. Point `grid` to the ASCII library or to the binary library's `.hdr`.
|
|
62
|
+
The fit uses linear interpolation. The reported error is conditional on the
|
|
63
|
+
fixed global parameters and continuum. The reduced χ² column comes from FERRE.
|
|
64
|
+
|
|
65
|
+
## Use from Python
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from aspcap import Grid, fit_abundance
|
|
69
|
+
|
|
70
|
+
grid = Grid.read("examples/toy-grid.dat")
|
|
71
|
+
result, pixels = fit_abundance(
|
|
72
|
+
grid, wavelength, flux, error, continuum,
|
|
73
|
+
{"ALPHA": 0.0, "METALS": -0.25},
|
|
74
|
+
star_id="my-star", species="Mg", label="ALPHA",
|
|
75
|
+
windows=[(15002, 15005)], add_labels=["METALS"],
|
|
76
|
+
)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Pass the four CSV columns as sequences. `result` is a dictionary and `pixels`
|
|
80
|
+
iterates over the diagnostic rows.
|
|
81
|
+
|
|
82
|
+
The FERRE protocol lives in `aspcap/ferre.py`, the abundance logic in
|
|
83
|
+
`aspcap/abundances.py`, and file input and output in `aspcap/cli.py`. Run
|
|
84
|
+
`python -m unittest discover -s tests -v` for the tests. Set
|
|
85
|
+
`ASPCAP_TEST_FERRE=/path/to/ferre` to include the integration test if FERRE
|
|
86
|
+
is outside your PATH.
|
|
87
|
+
|
|
88
|
+
## Documentation
|
|
89
|
+
|
|
90
|
+
The [user guide](https://github.com/dnidever/aspcap/blob/main/docs/index.rst) includes a quickstart, input reference,
|
|
91
|
+
[Spec1D guide](https://github.com/dnidever/aspcap/blob/main/docs/spectrum.rst), [continuum methods](https://github.com/dnidever/aspcap/blob/main/docs/continuum.rst), and
|
|
92
|
+
Python API. The repository includes Read the Docs build configuration. To
|
|
93
|
+
preview the guide locally after installing ASPCAP:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
python -m pip install -r docs/requirements.txt
|
|
97
|
+
python -m sphinx -W -b html docs docs/_build/html
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
The full-wavelength APOGEE DR17 M-giant test grid and detector weights are
|
|
102
|
+
included in ASPCAP installations.
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
python -m pip install --upgrade aspcap
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
See the [Grid walkthrough](https://github.com/dnidever/aspcap/blob/main/docs/grid.rst#fit-the-full-apogee-dr17-wavelength-grid)
|
|
109
|
+
for an example using the installed files.
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
## Multi-order spectra and external continuum fitting
|
|
113
|
+
|
|
114
|
+
`Spec1D` follows Doppler's `(pixels, orders)` layout, accepts unequal-length
|
|
115
|
+
orders, preserves masks/metadata, and can map detector chips back to a native
|
|
116
|
+
wavelength grid. It imports no Doppler fitting code or model loaders. Existing
|
|
117
|
+
Doppler objects can be used directly with the continuum functions.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from aspcap import Spec1D, continuum
|
|
121
|
+
|
|
122
|
+
spec = Spec1D(flux_orders, wave=wavelength_orders, err=error_orders)
|
|
123
|
+
result = continuum.fit(spec, method="chebyshev", degree=4)
|
|
124
|
+
normalized = result.apply(spec) # independent copy
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Choose the estimator with `continuum.fit(spec, method=...)`:
|
|
128
|
+
|
|
129
|
+
| Method | Useful options | Defaults |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| `chebyshev` / `polynomial` | `degree`, `weighted`, `clip` | Degree 4, unweighted, no clipping |
|
|
132
|
+
| `spline` | `degree`, `smoothing`, `weighted`, `clip` | Degree 3, noise-based smoothing |
|
|
133
|
+
| `median` | `window`, `mode` | 151 pixels, mirror edges |
|
|
134
|
+
| `percentile` | `percentile`, `bins` | 90th percentile, 20 bins |
|
|
135
|
+
| `model_ratio` | `model`, `window`, `ratio_bounds`, `mode` | Model required; 151 pixels; ratios 0.1–10 |
|
|
136
|
+
|
|
137
|
+
Methods operate independently per order. Use `result.fit_mask`, `result.flags`,
|
|
138
|
+
`result.parameters`, and `result.settings` to inspect the estimate. Invalid
|
|
139
|
+
orders raise an error by default; `on_failure="mask"` returns flagged, masked
|
|
140
|
+
results instead. `result.apply(spec)` returns a copy; `inplace=True` updates the
|
|
141
|
+
input explicitly. All normalization here is outside FERRE. Applying a
|
|
142
|
+
continuum rescales both flux and error; it does not include uncertainty in the
|
|
143
|
+
continuum estimate. See [the spectrum guide](https://github.com/dnidever/aspcap/blob/main/docs/spectrum.rst),
|
|
144
|
+
[the continuum guide](https://github.com/dnidever/aspcap/blob/main/docs/continuum.rst), and the runnable
|
|
145
|
+
[example](https://github.com/dnidever/aspcap/blob/main/examples/spectrum_continuum.py). The core design is adapted from
|
|
146
|
+
[Doppler Spec1D](https://github.com/dnidever/doppler/blob/6e165f87c6d766eff6eaa1fbee2c82be5bb30cc0/doppler/spec1d.py);
|
|
147
|
+
its MIT notice is retained in `LICENSE-DOPPLER`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Spectral data, external continuum normalization, and FERRE abundance fits."""
|
|
2
|
+
|
|
3
|
+
from .abundances import fit_abundance
|
|
4
|
+
from .ferre import Grid
|
|
5
|
+
from .spectrum import Spec1D
|
|
6
|
+
from . import continuum
|
|
7
|
+
|
|
8
|
+
__all__ = ["Grid", "fit_abundance", "Spec1D", "continuum"]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Fit an individual species using the continuum and labels of a global fit."""
|
|
2
|
+
|
|
3
|
+
from math import isclose, isfinite
|
|
4
|
+
import numpy as np
|
|
5
|
+
from .ferre import run_ferre
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def fit_abundance(grid, wavelength, flux, error, continuum, parameters, *,
|
|
9
|
+
star_id, species, label, windows, add_labels=(),
|
|
10
|
+
executable="ferre", keep_runs=False, run_root=None, mask=None):
|
|
11
|
+
"""Return a result row and a model on the native grid.
|
|
12
|
+
|
|
13
|
+
Inputs are in the grid's wavelength units and air/vacuum convention.
|
|
14
|
+
The optional mask uses True for unusable pixels.
|
|
15
|
+
Windows are inclusive (lower, upper) pairs with unit weights. The optional
|
|
16
|
+
add_labels are fixed global-fit coordinates added to report [X/H].
|
|
17
|
+
"""
|
|
18
|
+
npix = len(grid.wavelengths)
|
|
19
|
+
badmask = np.zeros(npix, bool) if mask is None else np.asarray(mask, dtype=bool)
|
|
20
|
+
if badmask.shape != (npix,):
|
|
21
|
+
raise ValueError("mask must have one Boolean value per grid pixel (True means bad)")
|
|
22
|
+
if any(len(values) != npix for values in (wavelength, flux, error, continuum)):
|
|
23
|
+
raise ValueError("Spectrum and continuum must have one row per grid pixel")
|
|
24
|
+
if not all(isclose(a, b, rel_tol=0, abs_tol=1e-4 * (grid.wavelengths[1] - grid.wavelengths[0]))
|
|
25
|
+
for a, b in zip(wavelength, grid.wavelengths)):
|
|
26
|
+
raise ValueError("Spectrum wavelengths do not match the native grid")
|
|
27
|
+
if label not in grid.labels:
|
|
28
|
+
raise ValueError(f"Unknown abundance label {label!r}. Available: {grid.labels}")
|
|
29
|
+
if (label in add_labels or len(set(add_labels)) != len(add_labels)
|
|
30
|
+
or any(name not in parameters or name not in grid.labels for name in add_labels)):
|
|
31
|
+
raise ValueError("Abundance conversion labels must be distinct fixed grid labels")
|
|
32
|
+
if not windows or any(not (isfinite(lo) and isfinite(hi) and lo <= hi)
|
|
33
|
+
for lo, hi in windows):
|
|
34
|
+
raise ValueError("Provide one or more finite wavelength windows (lower, upper)")
|
|
35
|
+
|
|
36
|
+
normalized_flux, normalized_error, weights = [], [], []
|
|
37
|
+
for wave, value, sigma, scale, bad in zip(wavelength, flux, error, continuum, badmask):
|
|
38
|
+
if not isfinite(scale) or scale <= 0:
|
|
39
|
+
raise ValueError("Global continuum values must be finite and positive")
|
|
40
|
+
value, sigma = value / scale, sigma / scale
|
|
41
|
+
good = not bad and isfinite(value) and isfinite(sigma) and sigma > 0
|
|
42
|
+
normalized_flux.append(value if good else 1.0)
|
|
43
|
+
normalized_error.append(sigma if good else 1e10)
|
|
44
|
+
weights.append(int(good and any(lo <= wave <= hi for lo, hi in windows)))
|
|
45
|
+
pixels_used = sum(weights)
|
|
46
|
+
if pixels_used < 2:
|
|
47
|
+
raise ValueError(f"{species}: fewer than two usable pixels in the element windows")
|
|
48
|
+
|
|
49
|
+
fit = run_ferre(grid, normalized_flux, normalized_error, parameters, label, weights,
|
|
50
|
+
executable=executable, keep_runs=keep_runs, run_root=run_root)
|
|
51
|
+
axis = grid.labels.index(label)
|
|
52
|
+
raw = fit.parameters[label]
|
|
53
|
+
low, step = grid.lower[axis], grid.step[axis]
|
|
54
|
+
high = low + (grid.counts[axis] - 1) * step
|
|
55
|
+
flags = []
|
|
56
|
+
if min(raw - low, high - raw) <= 0.125 * step:
|
|
57
|
+
flags.append("GRID_EDGE")
|
|
58
|
+
if fit.errors[label] is None:
|
|
59
|
+
flags.append("NO_UNCERTAINTY")
|
|
60
|
+
result = {
|
|
61
|
+
"star_id": star_id,
|
|
62
|
+
"species": species,
|
|
63
|
+
"grid_label": label,
|
|
64
|
+
"raw_value": raw,
|
|
65
|
+
"abundance_scale": f"[{species}/H]",
|
|
66
|
+
"abundance": raw + sum(parameters[name] for name in add_labels),
|
|
67
|
+
"conditional_error": fit.errors[label],
|
|
68
|
+
"ferre_reduced_chi2": fit.reduced_chi2,
|
|
69
|
+
"pixels_used": pixels_used,
|
|
70
|
+
"flags": "|".join(flags),
|
|
71
|
+
}
|
|
72
|
+
pixels = zip(wavelength, normalized_flux, normalized_error, fit.model, weights)
|
|
73
|
+
return result, pixels
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Command-line interface for a single HGE abundance fit."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import csv
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from . import Grid, fit_abundance
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _run(args):
|
|
12
|
+
path = args.config.resolve()
|
|
13
|
+
config = json.loads(path.read_text())
|
|
14
|
+
species = json.loads((path.parent / config["species_file"]).read_text())
|
|
15
|
+
grid = Grid.read(path.parent / config["grid"])
|
|
16
|
+
expected_frame = "vacuum" if grid.vacuum else "air"
|
|
17
|
+
if config["wavelength_frame"] != expected_frame:
|
|
18
|
+
raise ValueError(f"Grid uses {expected_frame} wavelengths")
|
|
19
|
+
with (path.parent / config["spectrum"]).open(newline="") as file:
|
|
20
|
+
rows = list(csv.DictReader(file))
|
|
21
|
+
columns = [[float(row[name]) for row in rows]
|
|
22
|
+
for name in ("wavelength", "flux", "error", "continuum")]
|
|
23
|
+
executable = args.ferre or config.get("ferre", "ferre")
|
|
24
|
+
if not args.ferre and "/" in executable:
|
|
25
|
+
executable = path.parent / executable
|
|
26
|
+
result, pixels = fit_abundance(
|
|
27
|
+
grid, *columns, config["parameters"], star_id=config["star_id"],
|
|
28
|
+
species=species["species"], label=species["label"], windows=species["windows"],
|
|
29
|
+
add_labels=species.get("add_labels", ()), executable=executable,
|
|
30
|
+
keep_runs=args.keep_runs, run_root=args.run_root,
|
|
31
|
+
)
|
|
32
|
+
if args.pixels:
|
|
33
|
+
with args.pixels.open("w", newline="") as file:
|
|
34
|
+
writer = csv.writer(file)
|
|
35
|
+
writer.writerow(("wavelength", "normalized_flux", "normalized_error", "model", "weight"))
|
|
36
|
+
writer.writerows(pixels)
|
|
37
|
+
with args.output.open("w", newline="") as file:
|
|
38
|
+
writer = csv.DictWriter(file, fieldnames=result)
|
|
39
|
+
writer.writeheader()
|
|
40
|
+
writer.writerow(result)
|
|
41
|
+
print(f"Wrote {args.output}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def main():
|
|
45
|
+
parser = argparse.ArgumentParser(prog="aspcap", description=__doc__)
|
|
46
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
47
|
+
abundances = commands.add_parser("abundances", help="fit an individual species")
|
|
48
|
+
abundances.add_argument("config", type=Path)
|
|
49
|
+
abundances.add_argument("--ferre", help="FERRE executable name or path")
|
|
50
|
+
abundances.add_argument("--output", type=Path, default=Path("abundance.csv"))
|
|
51
|
+
abundances.add_argument("--pixels", type=Path, help="save normalized data, model, and weights")
|
|
52
|
+
abundances.add_argument("--keep-runs", action="store_true")
|
|
53
|
+
abundances.add_argument("--run-root", type=Path, help="existing directory for FERRE run files")
|
|
54
|
+
args = parser.parse_args()
|
|
55
|
+
try:
|
|
56
|
+
if args.pixels and args.pixels.resolve() == args.output.resolve():
|
|
57
|
+
raise ValueError("--pixels and --output must use different files")
|
|
58
|
+
_run(args)
|
|
59
|
+
except (OSError, ValueError, KeyError, TypeError, RuntimeError) as error:
|
|
60
|
+
parser.exit(1, f"aspcap: {error}\n")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
main()
|