-
Notifications
You must be signed in to change notification settings - Fork 0
ES Modelo de sextete
⚙️ Página generada — no la edites aquí. Origen:
docs/modelo_sextete.md. Corrígela en el repositorio y el wiki se regenera solo.
Objetivo. Este documento describe exactamente cómo Fitbauer calcula el sextete magnético, para que cualquier otra implementación (en particular la web de la que partió originalmente el programa) pueda reproducir un ajuste idéntico. Es el contrato de referencia: si la web sigue este documento, dados los mismos parámetros producirá la misma absorción y la misma transmisión.
La implementación canónica vive en
core/physics.py,core/constants.pyycore/hamiltonian.py. Si este documento y el código discrepan, manda el código.
-
Eje de velocidad
ven mm/s. Signo conservado tal cual (puede ser negativo; ver folding en §7). -
Modelo de transmisión (no de absorción): el espectro observado baja desde una línea base. Para un solo componente:
T(v) = baseline + slope · v − A(v)donde
A(v) ≥ 0es la absorción del componente. Con varios componentes se suma la absorción totalA_tot = Σ A_cantes de restar (ver §6). -
Unidades de parámetros:
δ(delta),ΔE_Q(quad) y lasΓen mm/s;BHFen teslas (T); intensidades adimensionales;depthadimensional (escala de absorción).
Orden canónico (ver SEXTET_PARAM_NAMES en core/constants.py):
| # | Nombre | Símbolo | Significado |
|---|---|---|---|
| 0 | delta |
δ | Desplazamiento isómero (mm/s) |
| 1 | quad |
ΔE_Q | Desdoblamiento cuadrupolar de 1er orden (mm/s) |
| 2 | bhf |
B_hf | Campo hiperfino (T) |
| 3 | gamma1 |
Γ₁ | Anchura HWHM de las líneas 1 y 6 (mm/s) |
| 4 | gamma2 |
Γ₂(rel) | Anchura relativa de las líneas 2 y 5 |
| 5 | gamma3 |
Γ₃(rel) | Anchura relativa de las líneas 3 y 4 |
| 6 | depth |
d | Profundidad (escala global de absorción) |
| 7 | int1 |
I₁₃ | Intensidad relativa líneas 1,6 respecto a 3,4 |
| 8 | int2 |
I₂₃ | Intensidad relativa líneas 2,5 respecto a 3,4 |
| 9 | int3 |
I (base) | Intensidad base de las líneas 3,4 (convención NORMOS: fija a 1) |
Las seis líneas se indexan 1..6 de izquierda a derecha en velocidad.
Este es el tratamiento por defecto (treatment="1st_order"), el que debe replicar la web.
Las posiciones provienen del patrón de velocidad publicado de α-Fe, NO de los
momentos nucleares (ver §8). En core/constants.py:
_BASE_POSITIONS = [-10.657, -6.167, -1.677, 1.677, 6.167, 10.657] · 0.5
= [ -5.3285, -3.0835, -0.8385, 0.8385, 3.0835, 5.3285] (mm/s)
BHF_DEFAULT_T = 33.0
LINE_POS_33T = _BASE_POSITIONS # posiciones a 33.0 T exactos
⚠️ No derivar estas posiciones de los momentos nucleares. El cálculo "de libro" da un desdoblamiento ~0,4 % menor (línea externa 5.309 vs 5.328 mm/s) y sesga el BHF ~0,1 T hacia arriba. La constante 33.0 T es la calibración; un α-Fe debe ajustar a 33.0 T exacto, igual que NORMOS.
Las posiciones escalan linealmente con el campo y se desplazan por δ y por el patrón cuadrupolar:
LINE_QUAD_PATTERN = [ +0.5, −0.5, −0.5, −0.5, −0.5, +0.5 ]
posición_i = LINE_POS_33T[i] · (BHF / 33.0) + δ + ΔE_Q · LINE_QUAD_PATTERN[i]
Es decir, el campo escala el desdoblamiento magnético, δ desplaza el sextete completo, y el cuadrupolo de 1er orden separa el par externo (líneas 1,6: +ΔE_Q/2) del resto (líneas 2,3,4,5: −ΔE_Q/2).
i3 = int3 # líneas 3 y 4
i2 = int3 · int2 # líneas 2 y 5
i1 = int3 · int1 # líneas 1 y 6
pesos = [ i1, i2, i3, i3, i2, i1 ]
Con la convención NORMOS por defecto (int3 = 1, int2 = 2, int1 = 3) se obtiene el
patrón clásico 3 : 2 : 1 : 1 : 2 : 3.
gamma2 y gamma3 son multiplicadores relativos sobre gamma1, no anchuras
absolutas:
g1 = gamma1 # líneas 1 y 6
g2 = gamma1 · gamma2 # líneas 2 y 5
g3 = gamma1 · gamma3 # líneas 3 y 4
gammas = [ g1, g2, g3, g3, g2, g1 ]
Cada línea aporta peso · perfil(v; centro, γ). Hay dos perfiles seleccionables; el
estado global vive en core/physics.py (LINE_PROFILE_KIND, VOIGT_SIGMA).
L(v; v0, γ) = γ² / ( (v − v0)² + γ² )
γ es la HWHM. L(v0) = 1 en el centro.
Usando la función de Faddeeva w(z) (scipy.special.wofz), con σ = VOIGT_SIGMA:
denom = σ · √2
norm = σ · √(2π)
V(v; v0, γ) = Re[ w( ((v − v0) + iγ) / denom ) ] / norm
pico = Re[ w( iγ / denom ) ] / norm
perfil = V / pico # normalizado a 1 en el centro
La normalización es analítica al pico (no se divide por el máximo discreto de la malla). Esto evita subestimar el pico cuando la malla de velocidad no cae justo en
v0. La web debe normalizar igual para coincidir.
A_sextete(v) = depth · Σ_{i=1..6} pesos[i] · perfil(v; posición_i, gammas[i])
A_tot(v) = Σ_c A_c(v) # suma de todos los componentes activos
T(v) = baseline + slope · v − A_eff(v)
donde por defecto A_eff = A_tot (modelo de absorbente delgado, lineal). Existe un modo
opcional de absorbente grueso / saturación con amplitud C = sat_scale > 0:
A_eff(v) = C · ( 1 − exp(−A_tot(v) / C) ) # límite C→∞ recupera A_eff = A_tot
Si la web solo implementa el modelo delgado, debe usar A_eff = A_tot (equivale a no
activar saturación en Fitbauer).
Para que un ajuste sobre el mismo espectro coincida, el modelo directo (§3–§6) no
basta: hay que doblar igual, normalizar igual y construir el eje de velocidad
igual. Si la web dobla de otra forma, el BHF, las posiciones y las áreas saldrán
distintas aunque el modelo del sextete sea idéntico. La implementación canónica es
core/folding.py. Esta sección es la parte que más diverge entre implementaciones,
así que se especifica completa.
El detector registra N canales (típicamente 512) que contienen el espectro dos
veces (ida y vuelta del movimiento del transductor), espejados respecto a un centro
de simetría (el folding point). Doblar = promediar cada canal con su simétrico para
obtener N/2 puntos (típicamente 256) con mejor relación señal/ruido. El resultado se
ordena de velocidad negativa a positiva.
- Canales numerados 1..N (1-based), no 0-based. Esto importa para la fórmula.
- El
center(folding point interno) es el centro de simetría y puede ser fraccionario (p. ej. 255.77), no solo entero/semientero. -
Relación con Normos: el número que reporta Normos ("Final/Upper folding point")
suele estar en convención de espectro completo (≈ 511 para 512 canales) y es
aproximadamente el doble del centro interno de esta GUI (≈ 255.5). Conversión en
read_normos_folding_point: si el valor≥ 400→ espectro completo → se divide por 2; si< 400→ ya es semiespecro → se usa tal cual.
Genera siempre n_out = N // 2 puntos. Para cada punto j = 0 .. n_out−1:
distancia = j + 0.5
canal_izq = center − distancia
canal_der = center + distancia
folded[j] = 0.5 · ( C(canal_izq) + C(canal_der) )
donde C(canal) es la interpolación subcanal CÚBICA 1-based
(FOLD_INTERP_ORDER_DEFAULT = 3): una B-spline interpolante dentro del rango de canales
y extrapolación lineal fuera de él (una spline extrapolada oscila, y en los bordes
solo hay línea base).
Con order = 1 se recupera la interpolación lineal histórica
(interp_channel_1based, que se conserva como API):
# canales 1..N, valores counts[0..N-1]
si canal < 1: C = counts[0] + (canal − 1) · (counts[1] − counts[0]) # extrapola
si canal ≥ N: C = counts[N-1] + (canal − N) · (counts[N-1] − counts[N-2]) # extrapola
si no:
lo = floor(canal)
frac = canal − lo
si frac ≈ 0: C = counts[lo−1]
si no: C = (1 − frac)·counts[lo−1] + frac·counts[lo]
Clave para reproducir Normos: el doblado NO se queda en pares de canales enteros. Para centros fraccionarios interpola, y en los bordes extrapola linealmente en vez de perder un canal. Así siempre salen
N/2puntos.
Por qué cúbica y no lineal. La interpolación lineal es un filtro paso bajo: al promediar canales vecinos ensancha las líneas. Sobre un sexteto sintético con Γ = 0.28 mm/s y 512 canales, el Γ ajustado sale hasta un 9.5 % alto cuando la parte fraccionaria del canal emparejado es 0.5 (el peor caso); la cúbica reduce ese sesgo a un 2.8 %. El error escala como
f·(1−f), así que con centro semientero —el caso habitual, y el de todo el banco de validación—f = 0y las dos interpolaciones son idénticas. NORMOS evita el problema por otra vía: trunca el punto de doblado a entero y suma pares de canales enteros sin interpolar nunca, a costa de desalinear el par hasta medio canal, lo que ensancha igual que la interpolación lineal.La spline es global, así que se construye excluyendo los
edge_trimcanales de cada extremo al buscar el folding point: un canal 1 muerto (habitual en ADT reales) se propagaba hacia el interior y desplazaba el centro 0.056 canales.
Si no viene dado por Normos, se busca minimizando χ² de la diferencia entre canales simétricos:
- Malla de candidatos en pasos de 0.5 (por defecto 250.5 .. 262.5 para 512 canales).
- Para cada centro,
χ²(center) = Σ (counts[izq] − counts[der])²sobre los pares. - Se toma el centro de mínimo χ² y se interpola el mínimo con una parábola (refinamiento subcanal) usando los tres puntos alrededor del mínimo.
folded = fold_integer_or_half(counts, center)
# Recorta edge_trim canales en cada extremo (por defecto 1), si hay tamaño suficiente:
si edge_trim > 0 y folded.size > 2·edge_trim + 2:
folded = folded[edge_trim : −edge_trim]
norm = percentil_90(folded) # ≈ línea base
y = folded / norm # espectro normalizado (~1 en la base)
sigma = sqrt( max(folded/2, 1) ) / norm # ruido Poisson normalizado
-
Recorte de bordes: los canales extremos del espectro doblado son menos fiables y
se descartan (
edge_trim = 1por defecto). Nota: solo se recorta si el array es suficientemente grande (> 2·edge_trim + 2); en espectros reales de 256 puntos siempre aplica. - Normalización: por el percentil 90 de las cuentas dobladas (estimador robusto de la línea base), no por el máximo.
-
σ de Poisson:
sqrt(folded/2)(el/2viene de promediar dos canales) con suelo a 1 cuenta, dividido pornorm. Estossigmason los pesos del ajuste (ver §8).
full_n = N // 2
velocity = linspace(−Vmax, +Vmax, full_n) # eje COMPLETO primero
# se recortan las MISMAS posiciones de borde que en los datos:
si edge_trim > 0 y coincide el tamaño:
velocity = velocity[edge_trim : −edge_trim]
⚠️ Error clásico a evitar: NO reconstruir el eje comolinspace(−Vmax, +Vmax, n_points_recortado). Hay que construir el eje completo deN/2puntos y recortar los mismos bordes que en los datos. Reconstruirlo con menos puntos estira la escala de velocidad y sesga el BHF.
-
Vmaxpuede ser negativo; se conserva el signo (compatibilidad NORMOS/web con eje invertido).
Ejemplo reproducible con core/folding.py. Entrada: 8 canales (1-based)
counts = [100, 90, 70, 95, 98, 60, 88, 105]
Doblado a centro semientero center = 4.5 → pares (4,5) (3,6) (2,7) (1,8):
folded = [ 96.5, 65.0, 89.0, 102.5 ]
Doblado a centro fraccionario center = 4.30 (muestra la interpolación subcanal
cúbica; con order=1 daría [93.7, 70.8, 87.2, 101.8]):
folded = [ 95.734105, 67.493474, 87.304, 105.452211 ]
fold_and_normalize(counts, center=4.5, edge_trim=1) (en este array pequeño de 4
puntos doblados, edge_trim no recorta porque 4 ≤ 2·1+2):
norm = 100.7 # percentil 90
y = [ 0.958292, 0.645482, 0.883813, 1.017875 ]
sigma = [ 0.068979, 0.056612, 0.066245, 0.071091 ]
velocity_axis(N=8, Vmax=4.0, n_points=4, edge_trim=1):
velocity = [ −4.0, −1.333333, +1.333333, +4.0 ]
Si la web reproduce estos cuatro bloques (a tolerancia ~1e-6), el folding, la normalización y el eje son idénticos. Mantener este vector como test de regresión.
core/folding.py también lee parámetros de ficheros Normos asociados para inicializar
el ajuste (no afecta al modelo, solo a las semillas):
-
.RES: valores finalesISO→δ,BHF,QUA→ΔE_Q,WID(¡Normos da FWHM; la lorentziana interna usa HWHM = FWHM/2!),ARE→depth(víaARE / (π·Γ·Σpesos), conΣpesos = 2·(3+2+1) = 12). -
.JOB:VMAX,QUA(1). -
.PLT:Vmaxa partir de los bloques de 256 valores.
Dos implementaciones pueden compartir el modelo directo y aun así dar parámetros
ajustados distintos si minimizan cosas distintas. Fitbauer (core/fit_engine.py) usa:
- Pesos de Poisson (cuentas): residuo ponderado por la varianza ≈ cuentas.
- Mínimos cuadrados no lineales (TRF) con multistart determinista; opción de pérdida robusta y de evolución diferencial global.
- Límites físicos de parámetros (rangos de
BHF,ΔE_Q, etc.).
Para un ajuste idéntico al de Fitbauer, la web debe coincidir en: modelo directo (§3–§6) y esquema de pesos y mismos límites/semilla. Si la web solo necesita la misma curva dados parámetros (simulación), basta §3–§6.
Valores generados ejecutando core/physics.py (perfil Lorentziano, modo 1er orden).
Parámetros: δ=0, ΔE_Q=0, BHF=33.0, Γ₁=0.300, Γ₂rel=Γ₃rel=1.0,
depth=0.02, int1=3, int2=2, int3=1 → pesos 3:2:1:1:2:3, baseline=1, slope=0.
Γ₁ = 0.300es el valor HWHM que se pasa asextet_absorption(equivale a FWHM = 0.600 mm/s). Ojo con esta distinción: es la misma trampa que describe §7.8 para los sidecars de Normos.
Posiciones de las 6 líneas (mm/s):
[ −5.3285, −3.0835, −0.8385, +0.8385, +3.0835, +5.3285 ]
Absorción y transmisión muestreadas:
| v (mm/s) | A(v) | T(v) = 1 − A(v) |
|---|---|---|
| −5.3285 | 0.060236 | 0.939764 |
| −3.0835 | 0.040427 | 0.959573 |
| −0.8385 | 0.020497 | 0.979503 |
| 0.0 | 0.001524 | 0.998476 |
| +0.8385 | 0.020497 | 0.979503 |
| +3.0835 | 0.040427 | 0.959573 |
| +5.3285 | 0.060236 | 0.939764 |
Si la implementación de la web reproduce esta tabla (a tolerancia ~1e-6), el modelo directo del sextete es idéntico. Conviene mantener este vector como test de regresión en ambos lados.
Por defecto la web solo necesita el 1er orden (§3). Para completitud, core/physics.py
-
core/hamiltonian.pyofrecen dos tratamientos cuadrupolares más, por diagonalización del Hamiltonianoω_e·I_z + (ΔE_Q/6)(3 I_{z'}² − I(I+1))(EFG axial, η=0):
-
kundig_fixed: ángulo β fijo entre B y V_zz. -
kundig_powder: promedio policristal por cuadratura Gauss–Legendre den_quadorientaciones (β ∈ [0, π]).
Solo replicarlos si la web va a ofrecer esos modos.
- Posiciones base de α-Fe a 33.0 T =
±0.8385 / ±3.0835 / ±5.3285mm/s (§3.1). - Escalado lineal
· (BHF/33.0), desplazamiento+δ, patrón cuadrupolar[+0.5,−0.5,−0.5,−0.5,−0.5,+0.5]·ΔE_Q(§3.2). - Intensidades
[i1,i2,i3,i3,i2,i1]coni1=int3·int1,i2=int3·int2,i3=int3(§4.1). - Anchuras
gamma2/gamma3como multiplicadores degamma1(§4.2). - Lorentziana con pico = 1 (o Voigt con normalización analítica al pico) (§5).
- Transmisión
baseline + slope·v − A_tot; saturación opcional (§6). - Folding (§7) — solo si se compara sobre datos:
- Canales 1-based; centro de simetría posiblemente fraccionario (§7.2).
- Conversión del folding point Normos (≈ doble del centro interno) (§7.2).
- Doblado
0.5·(C(center−(j+0.5)) + C(center+(j+0.5)))con interpolación cúbica subcanal y extrapolación lineal en bordes (§7.3). - Recorte de bordes
edge_trim=1(§7.5). - Normalización por percentil 90 y
σ = sqrt(max(folded/2,1))/norm(§7.5). - Eje de velocidad construido completo y recortado, sin reconstruir con menos puntos (§7.6).
- Reproducir el contrato numérico de folding (§7.7).
- Mismo esquema de pesos Poisson (los
σde §7.5) y límites — solo si se exige el mismo ajuste, no solo la misma curva (§8). - Reproducir el vector de referencia del modelo directo de §9 como test.
Este documento es la especificación del lado Fitbauer. Para entregar un delta exacto (qué líneas cambiar en la web) conviene tener:
- El código actual de la web que calcula el sextete (fórmulas de posiciones, intensidades, perfil, transmisión) y el lenguaje/stack (JS, PHP, Python…).
- Si existe, el valor de campo de referencia que usa la web (¿32.95 T? ¿33.0 T?) y sus posiciones base — es la causa más probable de discrepancia histórica.
- Un espectro de prueba (p. ej. α-Fe) ajustado en ambos lados para comparar números.
© Jorge Sánchez Marcos, Nieves Menéndez González — Departamento de Química Física, UAM · Wiki generado desde docs/ para Fitbauer v5.0.0
Fitbauer v5.0.0
Start
- Home
- Installation
- User flows Data
- Folding
- Calibration (33 T)
- Spectrum comparison Fitting
- Distribution fitting
- Peak detection
- Profile likelihood
- Batch fitting Reference
- Command-line tools
- NORMOS (.JOB)
- Session format
- Sextet model (spec)
- Architecture
Inicio
- Inicio
- Instalación
- Flujos de usuario Datos
- Plegado (folding)
- Calibración (33 T)
- Comparación de espectros Ajuste
- Distribuciones
- Detección de mínimos
- Verosimilitud perfilada
- Ajuste en serie Referencia
- Línea de comandos
- NORMOS (.JOB)
- Formato de sesión
- Modelo de sextete (spec)
- Arquitectura