<div align="center">

# **PyLASP** - The Python Version of the LASP

[![Python 3.9+](https://img.shields.io/badge/Python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![SciPy](https://img.shields.io/badge/SciPy-Required-%236680aa.svg)](https://scipy.org/)
[![Astropy](https://img.shields.io/badge/Astropy-Required-%236680aa.svg)](https://www.astropy.org/)
[![Pandas](https://img.shields.io/badge/Pandas-Required-%236680aa.svg?)](https://pandas.pydata.org/)
[![PyTorch](https://img.shields.io/badge/PyTorch-Optional-bluegrey.svg)](https://pytorch.org/)
[![Matplotlib](https://img.shields.io/badge/Matplotlib-Optional-bluegrey.svg?)](https://matplotlib.org/)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)

---

</div>

## 📋 Table of Contents
<div>
    <ul>
        <li><a href="#overview"> Overview</a></li>
        <li><a href="#installation">Installation</a></li>
        <li><a href="#project-structure">Project Structure</a></li>
        <ul>
        <li><a href="#the-core-modules-in-lasp-curvefit">The Core Modules in LASP-CurveFit</a></li>
        <li><a href="#large-scale-applications-of-lasp-curvefit">Large-Scale Applications of LASP-CurveFit</a></li>
        <li><a href="#the-core-modules-in-lasp-adam-gpu">The Core Modules in LASP-Adam-GPU</a></li>
        <li><a href="#large-scale-applications-of-lasp-adam-gpu">Large-Scale Applications of LASP-Adam-GPU</a></li>
        </ul>
        <li><a href="#workflow">Workflow</a></li>
        <ul>
        <li><a href="#lasp-curvefit-inference-process">LASP-CurveFit Inference Process</a></li>
        <li><a href="#lasp-adam-gpu-inference-process">LASP-Adam-GPU Inference Process</a></li>
        </ul>
        <li><a href="#parameter-inference-example">Parameter Inference Example</a></li>
        <ul>
        <li><a href="#lasp-curvefit-inference-example">LASP-CurveFit Inference Example</a></li>
        <li><a href="#lasp-adam-gpu-inference-example">LASP-Adam-GPU Inference Example</a></li>
        </ul>
        <li><a href="#limitations-and-future-work">Limitations and Future Work</a></li>
        <li><a href="#citation">Citation</a></li>
        <li><a href="#license">License</a></li>
    </ul>
</div>

---

<h2 id="overview">🔭 Overview</h2>

**PyLASP** (The Python Version of the LAMOST Stellar Parameter Pipeline) is a modern, 
modular reimplementation of the original LASP (**LASP-MPFit**), which was developed in Interactive Data
Language (IDL) and employed the [`ULySS`](http://ulyss.univ-lyon1.fr/) software package to
infer stellar parameters from spectra. 

**PyLASP** refactors LASP into two modules: **LASP-CurveFit**, a CPU-based implementation
that preserves the original logic with enhanced I/O and multithreading; and **LASP-Adam-GPU**, a 
GPU-accelerated module that performs grouped optimization across spectra for efficient large-scale 
parameter inference.

**PyLASP** currently supports the inference of key stellar parameters—including radial
velocity, effective temperature, surface gravity, and metallicity—from LAMOST 
low-resolution spectra. Future versions will support the joint inference of multiple 
elemental abundances.

---

<h2 id="installation">🔧 Installation</h2>

Follow the steps below to set up **PyLASP** in a clean conda environment.
1. Create an independent conda environment:

```bash
conda create -n lasp-env python=3.10
```
2. Activate the environment:

```bash
conda activate lasp-env
```
3. Navigate to the LASP project folder:

```bash
cd /path/to/LASP

```

4. Install the package and its dependencies:
```bash

pip install -e .
```

⚠️ **Note:** The above steps install only the dependencies for **LASP-CurveFit**. To enable **LASP-Adam-GPU**, install the 
appropriate PyTorch version in the same environment. See 
the [`official PyTorch installation guide`](https://pytorch.org/get-started/locally/) for details.

---

<h2 id="project-structure">🖥️ Project Structure</h2>
<h3 id="the-core-modules-in-lasp-curvefit"> The Core Modules in LASP-CurveFit</h3>

```
LASP-CurveFit/
│
├── tgm_model/                                       # Spectral templates
│   └── elodie32_flux_tgm.fits                       # ELODIE polynomial spectral simulator coefficients
│
├── test_data/                                       # Sample data
│   ├── spec-57035-HD111424N200151V01_sp14-102.fits  # Single test spectrum
│   ├── LAMOST_DR10_APOGEE_DR16_CFI_10000.csv        # 10,000 random spectra sample
│   └── LAMOST_DR10_APOGEE_DR16_CFI.csv              # Complete sample dataset
│
├── file_paths.py                                    # Obtain the LASP file path
│
├── uly_read_lms/                                    # Spectrum data reading
│   ├── uly_spect_read_lms.py                        # LAMOST spectrum reader
│   ├── uly_spect_read_desi.py                       # DESI spectrum reader
│   ├── uly_spect_alloc.py                           # Initialize spectrum dictionary
│   ├── uly_spect_extract.py                         # Spectrum processing and extraction
│   └── uly_spect_get.py                             # Extract spectrum information
│
├── uly_tgm/                                         # Model spectrum structure
│   ├── uly_tgm.py                                   # Define model spectrum dictionary
│   └── uly_tgm_init.py                              # Initialize model spectrum dictionary
│
├── uly_tgm_eval/                                    # Model spectrum generation
│   └── uly_tgm_eval.py                              # Generate model spectra from parameters
│
├── WRS/                                             # Wavelength resampling
│   ├── xrebin.py                                    # Interpolation methods
│   └── uly_spect_logrebin.py                        # Spectrum resampling implementation
│
├── resolution_reduction/                            # Resolution matching
│   └── convol.py                                    # Spectral resolution reduction
│
├── legendre_polynomial/                             # Shape correction
│   └── mregress.py                                  # Legendre polynomial coefficient calculation
│
├── uly_fit/                                         # Parameter fitting core
│   ├── robust_sigma.py                              # Robust standard deviation calculation
│   ├── uly_fit_init.py                              # Initialize model spectrum dictionary
│   └── uly_fit_lin.py                               # Resolution reduction and pseudocontinuum
│
└── uly_tgm_alone/                                   # Single-spectrum optimization
    ├── tutorial_LASP_CurveFit.ipynb                 # LASP-CurveFit tutorial
    ├── test_LAMOST.py                               # Code validation example
    ├── ulyss.py                                     # Initialize observed spectrum info
    └── ulyss_fit_IDL_method.py                      # curve_fit parameter inference
```

<h3 id="large-scale-applications-of-lasp-curvefit">Large-Scale Applications of LASP-CurveFit</h3>

```
uly_tgm_alone/                                # Single-spectrum optimization applied to LAMOST
│
├── Ryzen9_7945HX_16c32t/                     # Large-scale parameter inference (Ryzen9_7945HX_16c32t)
│   │
│   ├── optimize_n_jobs/
│   │    ├── optimize_n_jobs_NoClean.ipynb    # Determine optimal n_jobs (NoClean)
│   │    └── optimize_n_jobs_Clean.ipynb      # Determine optimal n_jobs (Clean)
│   │
│   ├── CFI/                                  # CFI initial value strategy
│   │   ├── NoClean_curve_fit.ipynb           # Parameter inference (NoClean + CFI)
│   │   └── Clean_curve_fit.ipynb             # Parameter inference (Clean + CFI)
│   │
│   └── No_CFI/                               # Fixed initial value strategy
│       ├── NoClean_curve_fit.ipynb           # Parameter inference (NoClean + Fixed)
│       └── Clean_curve_fit.ipynb             # Parameter inference (Clean + Fixed)
│
└── XeonSilver4214_24c48t/                    # Large-scale parameter inference (XeonSilver4214_24c48t)
    │
    ├── optimize_n_jobs/
    │   ├── optimize_n_jobs_NoClean.ipynb     # Determine optimal n_jobs (NoClean)
    │   └── optimize_n_jobs_Clean.ipynb       # Determine optimal n_jobs (Clean)
    │
    ├── CFI/                                  # CFI initial value strategy
    │   ├── NoClean_curve_fit.ipynb           # Parameter inference (NoClean + CFI)
    │   └── Clean_curve_fit.ipynb             # Parameter inference (Clean + CFI)
    │
    └── No_CFI/                               # Fixed initial value strategy
        ├── NoClean_curve_fit.ipynb           # Parameter inference (NoClean + Fixed)
        └── Clean_curve_fit.ipynb             # Parameter inference (Clean + Fixed)

apply_to_DESI/                                # DESI application
│
└── LASP_CurveFit/
    ├── DESI_CurveFit_NoClean_7500.ipynb      # DESI stellar parameters (NoClean strategy)
    └── DESI_CurveFit_Clean_7500.ipynb        # DESI stellar parameters (Clean strategy)
```

---

<h3 id="the-core-modules-in-lasp-adam-gpu">The Core Modules in LASP-Adam-GPU</h3>

```
LASP-Adam-GPU/
│
├── config/
│   └── config.py                         # Data type configurations
│
├── file_paths.py                         # Obtain the LASP file path
│
├── data_to_pt/
│   ├── data_to_pt.ipynb                  # Step 1: Convert spectrum to .pt format
│   └── test_read_pt.ipynb                # Test reading .pt files
│
├── uly_tgm_eval/
│   └── uly_tgm_eval_pytorch.py           # Step 2: Generate N model spectra
│
├── WRS/
│   └── xrebin_pytorch.py                 # Step 3: Resample N model spectra to LAMOST wavelengths
│
├── resolution_reduction/
│   └── convol_pytorch.py                 # Step 4: Reduce the resolution of N model spectra to LAMOST spectra
│
├── legendre_polynomial/
│   └── mregress_pytorch.py               # Step 5: Correct the shape of N model spectra to match LAMOST spectra
│
│── clean_outliers/
│   └── clean_outliers.py                 # Step 6: Clean strategy
│
│── model_err/
│   ├── loss_reduced.py                   # Compute the flux residuals of N spectra
│   └── model_err.py                      # Step 9: Compute the parameter errors of N spectra
│ 
└── uly_tgm_group/
    │
    ├── ulyss_pytorch.py                  # Initialize spectra info for inference
    │
    ├── tutorial_LASP_Adam_GPU.ipynb      # LASP-Adam-GPU tutorial
    │   ├── section 10.7.9                # Step 7: Constructing the objective function
    │   └── section 10.5-10.7.13          # Step 8: Minimizing the objective function
    │
    └── Adam_lr/
        ├── CFI/lr_1e-5/lr_NoClean_Adam_RTX4060_Laptop.ipynb                   # CFI initialization at learning rate 1e-5
        ├── CFI/lr_1e-7/lr_NoClean_Adam_RTX4060_Laptop.ipynb                   # CFI initialization at learning rate 1e-7
        ├── NoCFI/lr_1e-5_Teff_5000K/lr_NoClean_Adam_RTX4060_Laptop.ipynb      # Teff=5000 K initialization at learning rate 1e-5
        ├── NoCFI/lr_1e-7_Teff_5000K/lr_NoClean_Adam_RTX4060_Laptop.ipynb      # Teff=5000 K initialization at learning rate 1e-7
        └── NoCFI/lr_1e-5_Teff_7500K/lr_NoClean_Adam_RTX4060_Laptop.ipynb      # Teff=7500 K initialization at learning rate 1e-5
```

<h3 id="large-scale-applications-of-lasp-adam-gpu">Large-Scale Applications of LASP-Adam-GPU</h3>

```
uly_tgm_group/                                               # Grouping optimization applied to LAMOST
│
├── GPU_RTX4060_Laptop/                                      # GPU_RTX4060_Laptop
│   │
│   ├── optimize_N/                                          
│   │    └── different_N_RTX4060_Laptop_NoClean.ipynb        # Determine optimal N (NoClean)
│   │
│   ├── CFI/                                                 # CFI initial value
│   │   ├── NoClean_Adam_RTX4060_Laptop.ipynb                # Parameter inference (NoClean + CFI)
│   │   └── Clean_Adam_RTX4060_Laptop.ipynb                  # Parameter inference (Clean + CFI)
│   │
│   └── No_CFI/                                              # Fixed initial value
│       ├── NoClean_Adam_RTX4060_Laptop.ipynb                # Parameter inference (NoClean + Fixed)
│       └── Clean_Adam_RTX4060_Laptop.ipynb                  # Parameter inference (Clean + Fixed)
│ 
├── GPU_RTX3090/                                             # GPU_RTX3090
│    │
│    ├── optimize_N/
│    │   └── different_N_RTX3090_NoClean.ipynb               # Determine optimal n_jobs (No Clean)
│    │
│    ├── CFI/                                                # CFI initial value
│    │   ├── NoClean_Adam_RTX3090.ipynb                      # Parameter inference (NoClean + CFI)
│    │   └── Clean_Adam_RTX3090.ipynb                        # Parameter inference (Clean + CFI)
│    │
│    └── No_CFI/                                             # Fixed initial value
│       ├── NoClean_Adam_RTX3090.ipynb                       # Parameter inference (NoClean + Fixed)
│       └── Clean_Adam_RTX3090.ipynb                         # Parameter inference (Clean + Fixed)
│ 
├── GPU_A100/                                                # GPU_A100
│    │
│    ├── optimize_N/
│    │   └── different_N_A100_NoClean.ipynb                  # Determine optimal n_jobs (NoClean)
│    │
│    ├── CFI/                                                # CFI initial value
│    │   ├── NoClean_Adam_A100.ipynb                         # Parameter inference (NoClean + CFI)
│    │   └── Clean_Adam_A100.ipynb                           # Parameter inference (Clean + CFI)
│    │
│    └── No_CFI/                                             # Fixed initial value
│        ├── NoClean_Adam_A100.ipynb                         # Parameter inference (NoClean + Fixed)
│        └── Clean_Adam_A100.ipynb                           # Parameter inference (Clean + Fixed)
│ 
└── LASP-Adam-CPU/                                           # LASP-Adam-CPU
    ├── different_N_Ryzen9_7945HX_16c32t_NoClean.ipynb       # Determine optimal n_jobs (NoClean + CFI)
    └── different_N_XeonSilver4214_24c48t_NoClean.ipynb      # Determine optimal n_jobs (NoClean + CFI)

apply_to_DESI/                                               # DESI application
│
└── LASP_Adam_GPU/
    ├── NoCFI_NoClean_1e-5.ipynb                             # DESI stellar parameters (NoClean strategy)
    └── NoCFI_Clean_1e-5.ipynb                               # DESI stellar parameters (Clean strategy)
```

---

<h2 id="workflow">🚀 Workflow</h2>

<h3 id="lasp-curvefit-inference-process">LASP-CurveFit Inference Process</h3>

**Step 1:** Read a target spectrum and store it in a dictionary: [`uly_spect_read_lms.py`](uly_read_lms/uly_spect_read_lms.py)

**Step 2:** Set the initial values for the parameters to be 
inferred, the Legendre polynomial order, the model location, and 
whether to enable the Clean strategy: [`ulyss.py`](uly_tgm_alone/ulyss.py)

**Step 3:** Generate the model spectra: [`uly_tgm_eval.py`](uly_tgm_eval/uly_tgm_eval.py)

**Step 4:** Resample the model spectra to LAMOST wavelengths: [`uly_spect_logrebin.py`](WRS/uly_spect_logrebin.py)

**Step 5:** Reduce the resolution of the model spectra to LAMOST spectra: [`convol.py`](resolution_reduction/convol.py)

**Step 6:** Correct the shape of the model spectra to match LAMOST spectra: [`mregress.py`](legendre_polynomial/mregress.py)

**Step 7:** Constructing the objective function: section 7.14.4.4 in [`ulyss_fit_IDL_method.py`](uly_tgm_alone/ulyss_fit_IDL_method.py)

**Step 8:** Minimizing the objective function: section 7.14 in [`ulyss_fit_IDL_method.py`](uly_tgm_alone/ulyss_fit_IDL_method.py)


<h3 id="lasp-adam-gpu-inference-process">LASP-Adam-GPU Inference Process</h3>

**Step 1:** Convert spectrum to .pt format: [`data_to_pt.ipynb`](data_to_pt/data_to_pt.ipynb)

**Step 2:** Generate N model spectra: [`uly_tgm_eval_pytorch.py`](uly_tgm_eval/uly_tgm_eval_pytorch.py)

**Step 3:** Resample N model spectra to LAMOST wavelengths: [`xrebin_pytorch.py`](WRS/xrebin_pytorch.py)

**Step 4:** Reduce the resolution of N model spectra to LAMOST spectra: [`convol_pytorch.py`](resolution_reduction/convol_pytorch.py)

**Step 5:** Correct the shape of N model spectra to match LAMOST spectra: [`mregress_pytorch.py`](legendre_polynomial/mregress_pytorch.py)

**Step 6:** Clean strategy (optional): [`clean_outliers.py`](clean_outliers/clean_outliers.py)

**Step 7:** Constructing the objective function: section 10.7.9 in [`tutorial_LASP_Adam_GPU.ipynb`](uly_tgm_group/tutorial_LASP_Adam_GPU.ipynb)

**Step 8:** Minimizing the objective function: section 10.5-10.7.13 in [`tutorial_LASP_Adam_GPU.ipynb`](uly_tgm_group/tutorial_LASP_Adam_GPU.ipynb)

**Step 9:** Compute the parameter errors of N spectra: [`model_err.py`](model_err/model_err.py)

---

<h2 id="parameter-inference-example">⚙️ Parameter Inference Example</h2>

<h3 id="lasp-curvefit-inference-example">LASP-CurveFit Inference Example</h3>

- **LASP-CurveFit** is used to infer one spectrum: [`tutorial_LASP-CurveFit.ipynb`](uly_tgm_alone/tutorial_LASP_CurveFit.ipynb)
- Individual spectrum parameter inference using `curve_fit`
- CPU-based implementation with multi-process support
- Preserves original IDL logic

<h3 id="lasp-adam-gpu-inference-example">LASP-Adam-GPU Inference Example</h3>

- **LASP-Adam-GPU** is used to infer N spectra simultaneously: [`tutorial_LASP-Adam-GPU.ipynb`](uly_tgm_group/tutorial_LASP_Adam_GPU.ipynb)
- Multi-spectrum parameter inference using `Adam`
- GPU-accelerated implementation based on `PyTorch`
- Significantly faster for large datasets
- Easy to extend to multi-element joint inference

---

<h2 id="limitations-and-future-work">🔄 Limitations and Future Work</h2>

| Feature                   | Current Status                             | Planned Improvement                                           |
|---------------------------|---------------------------------------------|----------------------------------------------------------------|
| Multi-Init Strategy       | Single initial guess per spectrum           | Support for multiple initializations to improve robustness     |
| Wavelength Segmentation   | Inference over a single continuous interval | Support for disjoint regions for feature-specific fitting      |
| Two-Stage Integration     | First-stage validated, not yet integrated   | Seamless coordination across initialization and both stages    |
| Multi-Abundance Inference | Only basic parameters inferred              | Joint inference of multiple elemental abundances               |

---

<h2 id="citation">📄 Citation</h2>

When using this code, please cite the following works:

1. [`ULySS: a full spectrum fitting package`](https://www.aanda.org/articles/aa/full_html/2009/27/aa11467-08/aa11467-08.html)
2. [`Coudé-feed stellar spectral library – atmospheric parameters`](https://www.aanda.org/articles/aa/full_html/2011/01/aa15014-10/aa15014-10.html)
3. [`The first data release (DR1) of the LAMOST regular survey`](https://iopscience.iop.org/article/10.1088/1674-4527/15/8/002)
4. [`Scalable Stellar Parameter Inference Using Python-Based LASP: From CPU Optimization to GPU Acceleration`]()

---

<h2 id="license">⚖ License</h2>

This project is released under the [`GNU General Public License v3.0`](https://www.gnu.org/licenses/gpl-3.0.html). See the [`LICENSE`](./LICENSE) file for details.

---
