Skip to content

How to set up peaks manually

In TL deconvolution, peak positions and kinetic parameters are generally constrained by the material under study. Defining each component explicitly — with physically motivated initial values and bounds — gives reproducible, interpretable results and is the standard approach for publication work.


Define each peak with PeakSpec

import tldecpy as tl

T, I = tl.load_refglow("x002")   # 4-peak FO benchmark, beta = 8.4 K/s

peaks = [
    tl.PeakSpec(
        name="P1",
        model="fo_rq",
        init={"Tm": 417.0, "Im": 12000.0, "E": 1.35},
        bounds={"Tm": (390.0, 435.0), "Im": (0.0, 40000.0), "E": (0.8, 2.2)},
    ),
    tl.PeakSpec(
        name="P2",
        model="fo_rq",
        init={"Tm": 456.0, "Im": 18000.0, "E": 1.48},
        bounds={"Tm": (435.0, 472.0), "Im": (0.0, 50000.0), "E": (0.8, 2.4)},
    ),
    tl.PeakSpec(
        name="P3",
        model="fo_rq",
        init={"Tm": 484.0, "Im": 28000.0, "E": 1.60},
        bounds={"Tm": (468.0, 500.0), "Im": (0.0, 70000.0), "E": (0.9, 2.6)},
    ),
    tl.PeakSpec(
        name="P4",
        model="fo_rq",
        init={"Tm": 512.0, "Im": 45000.0, "E": 2.00},
        bounds={"Tm": (498.0, 530.0), "Im": (0.0, 120000.0), "E": (1.0, 3.0)},
    ),
]

Units

Parameter Unit Typical range
Tm K 300 – 700 K for most TLD materials
Im detector counts same scale as measured I
E eV 0.5 – 3.0 eV for common TL materials
b (GO only) dimensionless 1.0 – 2.0
R (OTOR only) dimensionless 1e-6 – 0.90
alpha (MO only) dimensionless 0.001 – 0.999

Add a background component

# Explicit linear background
bg = tl.BackgroundSpec(
    type="linear",
    init={"a": 100.0, "b": 0.5},
)

# Or let autoinit decide (returns None for clean data)
_, bg_auto = tl.autoinit_multi(T, I, bg_mode="auto")

Available background types: "linear", "exponential", "none".

The linear background evaluates as \(a + b \cdot T\); the exponential form evaluates as \(a \cdot \exp(-T / c) + b\).


Run the fit

result = tl.fit_multi(
    T, I,
    peaks=peaks,
    bg=None,
    beta=8.4,
    options=tl.FitOptions(local_optimizer="trf"),
)

print(f"Converged: {result.converged}  FOM: {result.metrics.FOM:.3f} %")
for pk in result.peaks:
    p = pk.params
    print(f"  {pk.name}: Tm={p['Tm']:.2f} K  E={p['E']:.4f} eV  Im={p['Im']:.0f}")

Mix different models across peaks

You can assign a different kinetic model to each peak:

peaks_mixed = [
    tl.PeakSpec(name="P1", model="fo_rq",  init={"Tm": 420.0, "Im": 5000.0, "E": 1.2}),
    tl.PeakSpec(name="P2", model="go_kg",  init={"Tm": 460.0, "Im": 8000.0, "E": 1.5, "b": 1.6}),
    tl.PeakSpec(name="P3", model="otor_lw",init={"Tm": 490.0, "Im": 12000.0,"E": 1.7, "R": 0.1}),
]

Model keys

Call tl.list_models() to see all available canonical keys. Call tl.list_models("fo") to list only first-order variants.


Inspect bound violations after fitting

violations = {k: v for k, v in result.hit_bounds.items() if v}
if violations:
    print("Parameters at bounds — consider widening:")
    for name in violations:
        print(f"  {name}")

A parameter at its bound is a strong signal that the initial estimate is far from the optimum or that the bound is too tight.