Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-15, macos-13] # macos-15/14=arm64,macos-13=x86_64
os: [ubuntu-latest, macos-15] # macos-15/14=arm64,macos-13=x86_64
py: ['3.9','3.10','3.11','3.12','3.13']
name: py${{ matrix.py }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
Expand Down
3 changes: 3 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ sphinx:
python:
install:
- requirements: docs/requirements.txt
# Install the project itself so version metadata resolves from tags.
- method: pip
path: .
16 changes: 16 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Changelog

## 2026-02-10

- Added `derived_param` as the preferred name for derived-parameter callbacks in `solve()`.
- Kept backward compatibility with `dynamic_param`:
- If `dynamic_param` is used, a `DeprecationWarning` is emitted.
- If both `derived_param` and `dynamic_param` are provided (and differ), `ValueError` is raised.
- Updated internal solver logic and user-facing messages to use the "derived parameter" terminology.
- Added `smelib_lineinfo_mode` passthrough in `solve()` call paths (`_residuals` and `_jacobian`) so fitting runs can use SMElib precomputed line-info modes.
- Updated `linelist_mode` naming:
- Preferred values are now `"all"` and `"dynamic"`.
- `"auto"` is kept as a deprecated compatibility alias and maps to `"dynamic"` with a `DeprecationWarning`.
- Added segment-aware optional input `sme.wint` for synthesis transfer grids.
- Priority is now `sme.wint[segment]` first, then internal cached grids (when enabled), then SMElib adaptive grid generation.
- Updated user docs accordingly (`sme_struct`, `quickstart`, and `how-to`).
52 changes: 52 additions & 0 deletions docs/_static/custom.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
.home-cards {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1rem;
margin: 1.25rem 0 1.75rem;
}

.home-card {
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 13.5rem;
padding: 1.1rem 1rem;
border: 1px solid var(--pst-color-border, #d0d7de);
border-radius: 0.75rem;
background: var(--pst-color-surface, #f6f8fa);
text-decoration: none;
color: var(--pst-color-text-base, #24292f);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease;
}

.home-card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
border-color: var(--pst-color-primary, #2b6cb0);
text-decoration: none;
}

.home-card h3 {
margin: 0 0 0.5rem;
font-size: 1.35rem;
line-height: 1.2;
}

.home-card p {
margin: 0;
color: var(--pst-color-text-muted, #57606a);
}

.home-card-cta {
margin-top: 1rem;
align-self: flex-start;
font-weight: 600;
color: var(--pst-color-primary, #2b6cb0);
}

@media (max-width: 1050px) {
.home-cards {
grid-template-columns: 1fr;
}
}
Binary file added docs/_static/figures/first_spectrum.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_static/nlte_table.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions docs/advance/derived_param.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Derived Parameters

`derived_param` lets you define SME parameters as functions of other parameters
during fitting, without adding them to the free-parameter list.

## What It Does

In `solve(...)`, PySME:

1. sets the current free parameters,
2. evaluates each function in `derived_param`,
3. writes the returned value back to `sme`,
4. runs synthesis and computes residuals.

So derived parameters are updated at every iteration.

## API

```py
from pysme.solve import solve

sme = solve(
sme,
param_names=[...], # free parameters
derived_param={...}, # derived parameters
)
```

- `derived_param` is a `dict[str, callable]`.
- Key: SME parameter name (for example `"vmic"` or `"abund Mg"`).
- Value: function `f(sme) -> float`.

## Rules and Notes

- A parameter cannot be both free and derived in the same run.
- `dynamic_param` is still accepted as a legacy alias, but it is deprecated.
- For abundance keys (for example `"abund Mg"`), return the **final abundance**
you want in the usual abundance scale. PySME applies the internal
`monh` conversion for you when writing into `sme.abund`.

## Example 1: Tie `vmic` to `teff` and `logg`

```py
import numpy as np
from pysme.solve import solve

derived = {
"vmic": lambda s: np.clip(1.1 + 1e-4 * (s.teff - 5500.0) - 0.3 * (s.logg - 4.0), 0.2, 5.0)
}

fit = ["teff", "logg", "monh", "vsini"]
sme = solve(sme, fit, derived_param=derived)
```

Here `vmic` is never fitted directly; it is recomputed from the current model
state at each iteration.

## Example 2: Enforce fixed `[Mg/Fe]`

```py
from pysme.abund import Abund
from pysme.solve import solve

solar_mg = Abund.solar()["Mg"]

# Target relation: [Mg/Fe] = +0.20 -> A(Mg) = A_sun(Mg) + [M/H] + 0.20
derived = {
"abund Mg": lambda s: solar_mg + s.monh + 0.20
}

sme = solve(sme, ["teff", "logg", "monh"], derived_param=derived)
```

2 changes: 1 addition & 1 deletion docs/usage/faq.md → docs/advance/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Call `util.start_logging(filename)`.

```py
from SME import util
from pysme import util
util.start_logging("your_log_file.log")
```

Expand Down
20 changes: 18 additions & 2 deletions docs/usage/fordev.md → docs/advance/fordev.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ The follwing table shows the version matching between SMElib and PySME.

|PySME version|SMElib release version|SMElib version|
|:--:|:--:|:--:|
|v0.5.0-|latest (v6.13.x)|6.13 (June 2025)|
|v0.7.0-|latest (v6.13.x)|6.13 (June 2025)|
|v0.6.23|v6.13.12 (freezed)|6.13 (June 2025)|
|v0.4.199|v6.0.6 (freezed)|6.03 (July 2019)|
|v0.4.167-v0.4.198|v6.0.6 (not freezed)|6.03 (July 2019)|

Expand All @@ -85,6 +86,21 @@ The versions are controlled by `git tag`.
For PySME, setting a new tag will update the version to the latest tag.
For SMElib, setting a new release will based on a tag.

### Docs versioning policy (RTD)

PySME docs use a tag-driven versioning strategy.

1. Create release tags in `vX.Y.Z` format (for example `v0.6.23`).
2. `docs/conf.py` resolves `release` from `src/pysme/_version.py` (versioneer), so docs title/version follow git tags.
3. `.readthedocs.yaml` installs both docs requirements and the project itself, so tag metadata is available during RTD build.

RTD project settings must also be configured once (in the RTD web UI):

- Keep both `latest` (branch docs) and `stable` (latest release tag) enabled.
- Set default docs version to `stable`.
- Add an automation rule to activate SemVer tags (`v*`) as RTD versions.
- Hide or deactivate outdated branch versions if needed.

## NLTE departure coefficients

What happens when the NLTE grid is added into PySME?
Expand Down Expand Up @@ -150,4 +166,4 @@ Some extra test is needed to clear the situation.

### Installation

TBD.
TBD.
13 changes: 8 additions & 5 deletions docs/usage/how-to.md → docs/advance/how-to.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ The synthesis of long spectra, i.e., a spectra covering a wide wavelength range
The deafult synthesis mode of PySME takes all the lines in the `linelist` into account to synthesize the spectra, thus the synthesis takes a long time.
To make it even worse, the synthesis is repeated for each segment, thus if we have 10 segment covering from 3000-9000AA, all the lines (including those in ~9000AA) will be used for the first segment (3000-3600AA), and the total time cost is 10 times longer than just using one segment to cover the whole range.

This situation can be improved by using the central depth and line range parameters to select the relevant lines for each segment, by simply adding `linelist_mode='auto'` in `synthesize_spectrum`:
This situation can be improved by using the central depth and line range parameters to select the relevant lines for each segment, by simply adding `linelist_mode='dynamic'` in `synthesize_spectrum`:

```py
from pysme.synthesize import synthesize_spectrum
Expand All @@ -256,10 +256,13 @@ sme.wave = np.arange(3000, 6000, 0.02).reshape(-1, 100)
sme.linelist = line_list
sme.nlte.set_nlte('Na')
sme.cdr_depth_thres = 0.1
sme = synthesize_spectrum(sme, linelist_mode='auto')
sme = synthesize_spectrum(sme, linelist_mode='dynamic')
```

The detailed explanaiton of what `linelist_mode='auto'` triggers is as follow:
`linelist_mode='auto'` is still accepted as a deprecated compatibility alias
and maps to `linelist_mode='dynamic'`.

The detailed explanaiton of what `linelist_mode='dynamic'` triggers is as follow:

1. If the `sme.linelist.cdr_paras` is None (means `update_cdr` hasn't run for it), or either the Teff, logg, monh and vmic (optional) in `sme.linelist.cdr_paras` differ from the input parameters by 250K, 0.5, 0.5 or 1 (you can change these parameter in `sme.linelist.cdr_paras_thres` as a dictionary), then run `update_cdr` under current input stellar parameters.
2. Find the beginning and ending wavelength of each segment, wbeg and wend.
Expand Down Expand Up @@ -289,7 +292,7 @@ sme.spec = obs_flux
sme.uncs = obs_flux_err
sme.linelist = line_list
sme.cdr_depth_thres = 0.1
sme = solve(sme, linelist_mode='auto')
sme = solve(sme, linelist_mode='dynamic')
```

It only triggers the `linelist_mode` variable in `synthesize_spectrum`.
It only triggers the `linelist_mode` variable in `synthesize_spectrum`.
14 changes: 14 additions & 0 deletions docs/advance/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Advanced usage
========

This section collects advanced PySME functions, including derived-parameter
fitting, line-filtering strategies for performance, and developer-oriented notes.

.. toctree::
:maxdepth: 1

derived_param.md
line_filtering.md
how-to.md
fordev.md
faq.md
67 changes: 67 additions & 0 deletions docs/advance/line_filtering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Line Filtering

For wide wavelength coverage (or many segments), using the full line list in
every segment is expensive. PySME provides dynamic line filtering to keep only
relevant lines per segment (see Jian et al. in prep).

## Core Option

Use `linelist_mode` in synthesis or solve:

- `"all"`: use all lines (default).
- `"dynamic"`: filter lines by precomputed line properties (recommended for long spectra).
- `"auto"`: legacy alias of `"dynamic"` (deprecated).

## How Dynamic Filtering Works

When `linelist_mode="dynamic"`:

1. PySME ensures line metadata exists (`central_depth`, `line_range_s`, `line_range_e`).
If missing or stale, it updates them via `update_cdr(...)`.
2. For each segment, PySME keeps lines that overlap the segment range
(with broadening margin) and pass a strength threshold.
3. Only this reduced line subset is sent to SMElib for that segment.

This can significantly reduce runtime for long or segmented spectra.

## Main Controls

- `sme.cdr_depth_thres`: minimum line-strength threshold used in filtering.
- `sme.cdr_N_line_chunk`: chunk size used in `update_cdr`.
- `sme.cdr_parallel`: enable/disable parallel `update_cdr`.
- `sme.cdr_n_jobs`: number of parallel jobs.
- `cdr_database` / `cdr_create` (function args): reuse or build a CDR grid on disk.

## Example 1: Dynamic Filtering in Synthesis

```py
from pysme.synthesize import Synthesizer, synthesize_spectrum

synth = Synthesizer()
sme = synth.update_cdr(sme) # populate central_depth / line_range_* once
sme.cdr_depth_thres = 0.02 # keep only stronger lines

sme = synthesize_spectrum(sme, linelist_mode="dynamic")
```

## Example 2: Dynamic Filtering in Solve

```py
from pysme.solve import solve

fit = ["teff", "logg", "monh", "vmic"]
sme = solve(
sme,
fit,
linelist_mode="dynamic",
cdr_database="path/to/cdr_grid", # optional on-disk CDR cache/grid
cdr_create=False, # set True to force regeneration
)
```

## Practical Guidance

- Start with `sme.cdr_depth_thres = 0.0` and increase gradually if needed.
- Use `"all"` for short, narrow windows where filtering overhead may not help.
- Use `"dynamic"` for wide ranges or many segments.

6 changes: 3 additions & 3 deletions docs/usage/abundance.md → docs/concepts/abundance.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ values of H, He, and Li are approximately 3.16e4, 2.69e3, and

## Solar metallicity

PySME contains three pre defined sets of solar abundances,
PySME contains multiple pre defined sets of solar abundances
for you to choose from. They are:

From solar photosphere:
- `anders1989`: [Anders & Grevesse (1989, GeCoA)](https://ui.adsabs.harvard.edu/abs/1989GeCoA..53..197A)
- `grevesse1996`: [Grevesse, Noels & Sauval (1996, ASPC)](https://ui.adsabs.harvard.edu/abs/1996ASPC...99..117G)
- `grevesse1998`: [Grevesse, & Sauval (1998, SSRv)](https://ui.adsabs.harvard.edu/abs/1998SSRv...85..161G)
- `asplund2005`: [Asplund, Grevesse & Sauval (2005, ASPC)](https://ui.adsabs.harvard.edu/abs/2005ASPC..336...25A)
- `grevesse2007`: [Grevesse, Asplund & Sauval (2007, SSRv)](https://ui.adsabs.harvard.edu/abs/2007SSRv..130..105G)
- `grevesse2007` (alias: `solar`): [Grevesse, Asplund & Sauval (2007, SSRv)](https://ui.adsabs.harvard.edu/abs/2007SSRv..130..105G)
- `asplund2009`: [Asplund, Grevesse & Sauval (2009, ARA&A)](https://ui.adsabs.harvard.edu/abs/2009ARA&A..47..481A)
- `asplund2021`: [Asplund, Amarsi & Grevesse (2021, A&A)](https://ui.adsabs.harvard.edu/abs/2021A&A...653A.141A)

Expand Down Expand Up @@ -116,4 +116,4 @@ thus

- $K_\mathrm{H} = \left(1 + \sum_\mathrm{X}10^{H_\mathrm{X}-12} \right)^{-1}$
- $K_\mathrm{He} = \left(10^{12-H_\mathrm{He}} + 1 + 10^{12-H_\mathrm{He}}\times\sum_\mathrm{X}10^{H_\mathrm{X}-12} \right)^{-1}$
- $K_\mathrm{X} = H_\mathrm{X}-12-\log{(1+10^{H_\mathrm{He}-12})}$
- $K_\mathrm{X} = H_\mathrm{X}-12-\log{(1+10^{H_\mathrm{He}-12})}$
22 changes: 21 additions & 1 deletion docs/usage/atmosphere.md → docs/concepts/atmosphere.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,27 @@ The atmopshere object has the following fields:
|`wlstd`|Wavelength for continuum optical depth scale.|Å, Default value: 5000Å|
|`opflag`|Flags that indicate whether to enable various opacity packages during the radiative transfer calculation||

## Grid atmospheres
## Atmosphere grids:

- recommended:
- marcs2012.sav [(Gustafsson et al. 2008)](https://ui.adsabs.harvard.edu/abs/2008A%26A...486..951G)
- marcs2012p_t0.0.sav
- marcs2012p_t1.0.sav
- marcs2012p_t2.0.sav
- marcs2012s_t1.0.sav
- marcs2012s_t2.0.sav
- marcs2012s_t5.0.sav
- marcs2012t00cooldwarfs.sav
- marcs2012t01cooldwarfs.sav
- marcs2012t02cooldwarfs.sav

- deprecated:
- atlas12.sav
- atlas9_vmic0.0.sav
- atlas9_vmic2.0.sav
- ll_vmic2.0.sav

### Grid plots

![](../img/atmosphere/marcs2012_grid.png)
![](../img/atmosphere/marcs2012p_t0.0_grid.png)
Expand Down
Loading
Loading