from ..output_files_definitions import (
OutputFileValueDefinition as V,
create_output_file_definition,
OutputFileDefinition,
BlankSeparator,
OutputFileSectionDefinition,
)
from typing import Optional
import numpy as np
from ase.units import Rydberg
from ase2sprkkr.physics.broadening import create_lorentz_broadener, create_gaussian_broadener, compute_wlortab
from scipy.interpolate import interp1d
from ..output_files import CommonOutputFile
from ...common.grammar_types import Array, Keyword, Char, Table, Sequence, Complex, Real
from ...common.generated_configuration_definitions import GeneratedValueDefinition
from ...common.configuration_definitions import KeywordSeparator, SeparatorDefinition, BaseDefinition
from ...gui.plot import Multiplot, set_up_common_plot, single_plot
from ...common.decorators import cached_property, add_to_signature
[docs]
class RATOutputFile(CommonOutputFile):
extension = "rat"
plot_parameters = {}
"""
Output file for XAS (X-ray Absorption Spectroscopy) and related spectra (XMCD, spin and orbital sum-rule integrands).
"""
[docs]
def energies(self, group=0):
"""Energies in Ev relative to E-Fermi."""
efermi = self.EFERMI()
return np.array([(i.ENERGY()[0].real - efermi) * Rydberg for i in self.GROUPS[group].DATA.values()])
@property
def absorbing_element(self):
"""Chemical symbol of the element whose core spectrum is plotted."""
type_index = self.GROUPS[0].IT() - 1
return str(self.TYPES[type_index]["TXT_T"]).strip()
@property
def system_formula(self):
"""Build the system formula from the atom types and their multiplicities."""
parts = []
for atom_type in self.TYPES():
symbol = str(atom_type["TXT_T"]).strip()
count = int(atom_type["NAT"])
parts.append(symbol if count == 1 else f"{symbol}{count}")
return "".join(parts)
@property
def spectrum_subject(self):
return f"of {self.absorbing_element} in {self.system_formula}"
@property
def xray_edge(self):
"""Spectroscopic label of the absorbing core edge, for example L₂,₃."""
n = self.GROUPS[0].NCXRAY()
l = self.GROUPS[0].LCXRAY()
shells = "KLMNOPQ"
shell = shells[n - 1] if 1 <= n <= len(shells) else f"n={n}"
if n == 1:
return shell
subshell = "1" if l == 0 else f"{2 * l},{2 * l + 1}"
return rf"{shell}_{{{subshell}}}"
[docs]
def order(self):
"""Order of the energie dataset."""
[docs]
def validate_data(self):
energies = self.energies()
for i in range(1, len(self.GROUPS)):
if not (self.energies(i) != energies):
raise ValueError(f"Set of energies for GROUP={i} differs from the ones for GROUP=0")
cst = self.core_state_types
if len(cst[0]) > 2:
raise ValueError("Too much KAP core-state-types")
if len(cst[0]) == 2:
itr = iter(cst[1])
changes = 0
prev = next(i)
for i in itr:
if prev != i:
changes += 1
if changes >= len(cst[0]):
raise ValueError("KAP types can't be interleaved")
[docs]
@cached_property
def core_states_index(self):
"""Return indexes of core_states in CORE_STATES table, some of them can have two lines"""
STATES = self.GROUPS[0].CORE_STATES
return [i for i in range(len(STATES)) if i == 0 or STATES[i]["ICST"] != STATES[i - 1]["ICST"]]
[docs]
@cached_property
def core_state_types(self):
"""classify the core states according to the KAP property"""
return np.unique(self.GROUPS[0].CORE_STATES["KAP"], return_inverse=True)
[docs]
def core_states_energies(self, no_core_splitting=False):
states_index = self.core_states_index
n_groups = len(self.GROUPS)
out = np.empty((n_groups, len(states_index)))
def fill_group(i):
out[i] = self.GROUPS[i].CORE_STATES["E(Ry)"][states_index] * Rydberg
if no_core_splitting:
fill_group(0)
out[1:] = out[0]
else:
for i in range(n_groups):
fill_group(i)
return out
[docs]
def lorentz_width(self, source="campbell-papp"):
"""
Return tuple of Lorentz widths (for the two possible KAP types).
The `source` argument is forwarded to the core_hole_width lookup.
"""
from ase2sprkkr.physics.core_hole_width import core_hole_width
from ase.data import atomic_numbers
atomic_number = atomic_numbers[self.absorbing_element]
nc = self.GROUPS[0].NCXRAY()
lc = self.GROUPS[0].LCXRAY()
return (
core_hole_width(atomic_number, nc, lc, 1, source=source),
core_hole_width(atomic_number, nc, lc, 2, source=source),
)
[docs]
def broadener(self, energies, gauss_width, lorentz_width, n_valence=None, wlortab=None):
if lorentz_width is None:
lorentz_width = self.lorentz_width()
if n_valence is None:
from ase2sprkkr.physics.atomic_data import valence_electrons
n_valence = valence_electrons(self.absorbing_element)
# Accept Python floats and NumPy scalar floats here
if np.isscalar(lorentz_width):
lorentz_width = [float(lorentz_width)]
# Allow user to pass a precomputed wlortab array; otherwise compute from n_valence
if wlortab is None:
wlortab = compute_wlortab(energies, n_valence)
lorentz = [
None if w < 0.001 and n_valence > 0 else create_lorentz_broadener(energies, w, wlortab)
for w in lorentz_width
]
if len(lorentz) == 1:
lorentz *= 2
if gauss_width > 0.001:
gauss = create_gaussian_broadener(energies, gauss_width, method="dense")
else:
gauss = None
def broaden(data, k):
if lorentz[k]:
data = lorentz[k](data)
if gauss:
data = gauss(data)
return data
return broaden
@property
def data(self):
if not hasattr(self, "_data"):
raise ValueError("First call the `generate_data` method to compute the data")
return self._data
[docs]
def plot(
self,
layout=(None, 2),
figsize=None,
latex=None,
filename: Optional[str] = None,
show: Optional[bool] = None,
dpi=800,
separate_plots=False,
interpolate_to_fermi=True,
interpolation_threshold=1e-4,
zero_below=True,
zero_below_num=50,
zero_below_energy=25,
no_core_splitting=False,
merge_all=None,
updown_layout=True,
gauss_width=0.1,
lorentz_width=None,
n_valence=None,
core_hole_width="campbell-papp",
mld=False,
layout_kind="constrained",
**kwargs,
):
data = self.generate_data(
interpolate_to_fermi,
interpolation_threshold,
zero_below,
zero_below_num,
zero_below_energy,
no_core_splitting,
merge_all,
gauss_width,
lorentz_width,
n_valence,
core_hole_width,
mld,
)
num = data["XAS"].shape[0] # 1 or 2
if "SPIN" in data:
num += 1
num = num * 2
if figsize is None:
figsize = lambda layout: (layout[0] * 4, layout[1] * 4)
with Multiplot(
layout=layout,
figsize=figsize,
latex=latex,
filename=filename,
show=show,
dpi=dpi,
updown_layout=updown_layout,
separate_plots=separate_plots,
layout_kind=layout_kind,
number_of_plots=num,
**kwargs,
) as mp:
self.XAS.plot(multiplot=mp, data_generated=True, **kwargs)
self.XMCD.plot(multiplot=mp, data_generated=True, **kwargs)
if "SPIN" in data:
self.SPIN.plot(multiplot=mp, data_generated=True, **kwargs)
self.ORBIT.plot(multiplot=mp, data_generated=True, **kwargs)
[docs]
def generate_data(
self,
interpolate_to_fermi=True,
interpolation_threshold=1e-4,
zero_below=True,
zero_below_num=50,
zero_below_energy=25,
no_core_splitting=False,
merge_all=None,
gauss_width=0.0,
lorentz_width=None,
n_valence=None,
core_hole_width="campbell-papp",
mld=False,
):
key = {**locals()}
del key['self']
ckey = getattr(self, '_data_key', None)
if ckey:
for i,v in key.items():
if ckey[i] != v:
break
else:
return self._data
self._data = self.compute_data(**key)
self._data_key = key
return self._data
[docs]
def compute_data(
self,
interpolate_to_fermi=True,
interpolation_threshold=1e-4,
zero_below=True,
zero_below_num=50,
zero_below_energy=25,
no_core_splitting=False,
merge_all=None,
gauss_width=0.0,
lorentz_width=None,
n_valence=None,
core_hole_width="campbell-papp",
mld=False,
):
"""core energies"""
ktypes, type_map = self.core_state_types
n_ktypes = len(ktypes)
groups = [np.where(type_map[self.core_states_index] == i)[0].tolist() for i in range(len(ktypes))]
if len(groups) > 1:
if groups[0][0] > groups[1][0]:
groups = groups[::-1]
e_core = self.core_states_energies(no_core_splitting)
e_core_avg = np.empty(n_ktypes)
for i in range(n_ktypes):
e_core_avg[i] = np.average(e_core[:, groups[i]])
dcormin = float("inf")
border = len(groups[0])
if n_ktypes > 1:
for i in e_core:
dcormin = min(dcormin, np.min(np.abs(i[:border, np.newaxis] - i[border:])))
""" energies """
energies = self.energies()
order = np.argsort(energies)
senergies = energies[order]
if senergies[-1] > 650:
n_valence = 0
""" replace energies below e-fermi and add e-fermi energy """
fidx = np.searchsorted(senergies, 0.0)
if fidx >= len(senergies):
raise ValueError("Fermi energy not in the range of energies")
if fidx == 0:
nef = 0
elif abs(senergies[fidx]) < abs(senergies[fidx - 1]):
nef = fidx
else:
nef = fidx - 1
if interpolate_to_fermi:
if abs(senergies[nef]) < interpolation_threshold:
interpolate_to_fermi = False
def make_slice(order):
"""transform order array to slice, if possible - check for subsequent numbers"""
if len(order) == 0:
return slice(0, 0)
if np.all(order[:-1] < order[1:]) and 2 * np.sum(order) == (order[-1] - order[0] + 1) * (
order[0] + order[-1]
):
return slice(order[0], order[-1] + 1)
return order
if zero_below or interpolate_to_fermi:
if interpolate_to_fermi:
upper_half_idx_source = upper_half_idx = nef
# weights for interpolation to fermi
if ((nef == 0) or (senergies[nef] <= 0.0)) and (nef < len(senergies) - 1):
interp_idx = nef + 1
else:
interp_idx = nef - 1
x1, x2 = senergies[nef], senergies[interp_idx]
df = x2 - x1
w1, w2 = -x2 / df, x1 / df
my_fermi = 0.0
else:
upper_half_idx_source = upper_half_idx = nef
my_fermi = senergies[nef]
if zero_below:
senergies = np.concatenate(
[
abs(zero_below_energy) * (np.linspace(-1.0, 0.0, zero_below_num, endpoint=False) ** 3),
senergies[upper_half_idx:],
]
)
upper_half_idx = zero_below_num
if interpolate_to_fermi:
senergies[upper_half_idx] = 0
size = len(senergies)
# Energy, Group, State, Polarization
STATES = self.GROUPS[0].CORE_STATES
n_states = STATES[-1]["ICST"]
n_pol = len(self.NPOL())
shape = (size, len(self.GROUPS), n_states, n_pol)
source_shape = (len(energies), len(self.GROUPS), n_states, n_pol)
def read_rdt(name):
"""Fill the below fermi with either zero or data
fill above the fermi with data
And finally, the middle fermi-energy point set as interpolation.
"""
source = np.empty(source_shape)
for i, g in enumerate(self.GROUPS.values()):
for j, d in enumerate(g.DATA.values()):
source[j, i] = d.DATA[name].real.reshape((-1, n_pol))
source = source[order]
out = np.empty(shape)
if zero_below:
out[:upper_half_idx] = 0.0
out[upper_half_idx:] = source[upper_half_idx_source:]
else:
out[:] = source
if interpolate_to_fermi:
out[upper_half_idx] = w1 * source[nef] + w2 * source[interp_idx]
return out
rd = read_rdt("ABSRTA")
rt = read_rdt("ABSRATE")
if n_pol == 3:
if mld:
# XBand's MLD mode compares the average of the two circular
# polarizations with the z polarization.
rd[:, :, :, 1] = 0.5 * (rd[:, :, :, 0] + rd[:, :, :, 1])
rd = rd[:, :, :, 1:]
rt[:, :, :, 1] = 0.5 * (rt[:, :, :, 0] + rt[:, :, :, 1])
rt = rt[:, :, :, 1:]
else:
# RDRXAS reads all three columns, but XBand's default MCD
# mode subsequently sets NPOL=2 and uses the + and - columns.
rd = rd[:, :, :, :2]
rt = rt[:, :, :, :2]
n_pol = 2
# If caller didn't provide explicit lorentz_width, compute using requested dataset
if lorentz_width is None:
lorentz_width = self.lorentz_width(core_hole_width)
broaden = self.broadener(senergies, gauss_width, lorentz_width, n_valence)
borders = [0, border, n_states]
for i in range(n_ktypes):
rd[:, :, borders[i] : borders[i + 1]] = broaden(rd[:, :, borders[i] : borders[i + 1]], i)
rt[:, :, borders[i] : borders[i + 1]] = broaden(rt[:, :, borders[i] : borders[i + 1]], i)
if merge_all is None:
merge_all = abs(e_core_avg[0] - e_core_avg[-1]) < (senergies[-1] - senergies[0])
if n_ktypes <= 1 or no_core_splitting:
merge_all = False
"""
Výpočet SD
"""
def shift_by_e_core_avg_diff(data, energies=senergies, borders=borders, out=None, ref_avg=None, clip=False):
"""
Interpolate `data` shifted by core-energy differences onto `energies`.
Parameters
- data: array shaped (len(senergies), groups, states, n_pol)
- energies: target energy grid where the shifted spectra are evaluated
- borders: grouping borders as used elsewhere in this function
- out: optional pre-allocated output array; if None a new array is created
- out: optional pre-allocated output array; if None a new array is created.
Returns the output array (the same object passed as `out` when provided).
"""
# determine evaluation grid and output view
N = len(energies)
if out is None:
out = np.empty((N,) + data.shape[1:])
eval_energies = energies
out_view = out
for i in range(e_core.shape[0]):
for k in range(n_ktypes):
for j in range(borders[k], borders[k + 1]):
if ref_avg is None:
de = e_core_avg[k] - e_core[i, j]
else:
de = ref_avg - e_core[i, j]
shifted = eval_energies - de
for p in range(n_pol):
f = interp1d(senergies, data[:, i, j, p], kind="cubic", fill_value="extrapolate")
out_view[:, i, j, p] = f(shifted)
# values corresponding to evaluation points outside source grid
if clip:
out_view[shifted < senergies[0], i, j, p] = 0.0
out_view[shifted > senergies[-1], i, j, p] = data[-1, i, j, p]
return out
if merge_all:
ie = np.searchsorted(senergies, dcormin, side="right")
if ie >= len(senergies):
raise ValueError("No E(i) > Dcormin found")
shifted_energies = np.concatenate((senergies[:ie], senergies[upper_half_idx:] - my_fermi + dcormin))
kref = np.argmax(e_core_avg)
avg = e_core_avg[kref]
# Use shift_by_e_core_avg_diff to fill ad/at on the merged energy grid
# pass ref_avg so all states are shifted to the same reference (kref)
ad = shift_by_e_core_avg_diff(rd, energies=shifted_energies, borders=borders, ref_avg=avg, clip=True)
at = shift_by_e_core_avg_diff(rt, energies=shifted_energies, borders=borders, ref_avg=avg, clip=True)
df = avg - e_core
ebot = shifted_energies[0] + np.max(df)
iebot = np.searchsorted(shifted_energies, ebot, side="right")
etop = senergies[-1] + np.min(df)
ietop = np.searchsorted(shifted_energies, etop, side="left")
shifted_energies = shifted_energies[iebot:ietop]
ad = ad[iebot:ietop]
at = at[iebot:ietop]
# Recompute per-ktype `sd` on the merged energy grid (preserve per-ktype shifts)
sd = shift_by_e_core_avg_diff(rd, energies=shifted_energies, borders=borders, clip=True)
senergies = shifted_energies
else:
sd = shift_by_e_core_avg_diff(rd)
ad = sd.copy()
at = shift_by_e_core_avg_diff(rt)
# ------ combine spectra with common KAPPA for core state
shape = ad.shape[:2] + (n_ktypes, n_pol)
rd = np.empty(shape)
rt = np.empty(shape)
for i in range(n_ktypes):
rd[:, :, i] = ad[:, :, borders[i] : borders[i + 1]].sum(axis=2)
rt[:, :, i] = at[:, :, borders[i] : borders[i + 1]].sum(axis=2)
index = [i.IT() - 1 for i in self.GROUPS.values()]
concealment = np.asarray(self.TYPES()["CONC"]).ravel()[index]
nqt = np.asarray(self.TYPES()["NAT"]).ravel()[index]
weight = (concealment * nqt)[None, :, None, None]
def sum_groups(data):
data *= weight
return data.sum(axis=1)
if merge_all:
rd = rd.sum(axis=2)[:, :, None]
rt = rt.sum(axis=2)[:, :, None]
rd = sum_groups(rd)
rt = sum_groups(rt)
tt = 0.5 * (rd[:, :, -1] + rd[:, :, 0]).T
td = 0.5 * (rd[:, :, -1] - rd[:, :, 0]).T
out = {"XAS": tt, "XMCD": td, "ENERGY": senergies, "DICHROISM": "MLD" if mld else "XMCD"}
if n_ktypes > 1:
_sd = np.empty(shape)
for i in range(n_ktypes):
_sd[:, :, i] = sd[:, :, borders[i] : borders[i + 1]].sum(axis=2)
sd = sum_groups(_sd) # now (energy, ktype, n_pol) shape
yd = 0.5 * (sd[:, :, 1] - sd[:, :, 0])
spin = 3 * (yd[:, 1] - 2 * yd[:, 0])
orb = 2 * (yd[:, 1] + yd[:, 0])
out["SPIN"] = spin
out["ORBIT"] = orb
return out
"""
Czech notes - mimics the XBAND plot
// NCXRAY(ITXG) = principal quantum number n of the absorbing core orbital.
// LCXRAY(ITXG) = orbital angular momentum l of that core orbital.
TODO * CORE STATES se opakuje, ale N a L musí být stejné pro všechny skupiny
?? asi OK * očísluje grupy tak, že jde číslem grupy chodit rovnou
do tabulky typů
OK * vytvoří seřazenou tabulku indexů energií E
- ty by měly být stejné pro všechny grupy
OK * odečte od CORE_STATES.E(Ry) a ENERGY[0] Fermi enegy (EFERMI)
OK * najde energii nejblíže k FE (NEF je index, DIST vzdálenost)
OK * pokud DIST > 1E-4 tak provede lineární interpolaci do EFERMI
z dvou nejbližších bodů (starý kód přepíše ten nejbližší bod)
energies udělány
OK * pro XAS/CHIXAS
- vyhodí cokoli pod Fermi level
- pokud ne NOTABEXT tak přidá samé nuly na n=50 energií pod Fermi level
do souřadnic - 25Ev * (i/n)**3
??? * pokud je rozdíl nějakých energií menší nežli 1e-6, vynásobí tu energii 1.00001
* rozdělí CST podle KAP, mohou být jen dva typy - pole KTYP
OK - pokud poslední energie > 50, nastav NVAL=0, jinak NVAL=number_of_valence_electrons_of_the_first_atom (může být předefinováno)
OK * Načte wcorehole(atomic_number, N(1),L(1),IK=1,2 pro počet typů)
OK * core-spliting (vypnutelné parametrem) - pokud je vypnuto
CORE_STATES[i,j].E(Ry) = CORE_STATES[0,0].E(Ry)
OK * ECOREAV(KTYP) = avg( CORE_STATES[i,j].E(Ry) for given KTYP)
- pokud ECOREAV(i) - ECOREAV(j) < E(1) - E(n) (v abs)
nastav MERGE_ALL
OK * dormin, dcormax min a max abs(rozdílů) všech CORE_STATES[i,j].E(Ry) pro core
s ROZDILNYM KTYP. Asi jde udělat tak, že se najde min a max energií pro každý
ktyp a pak se to odečte.
OK seřadí data v RD/RT (skupiny) podle energií E
RD/RT jsou REAL(ABSRTA), REAL(ABSRATE)
OK broaodening RD a RT pro datasety dle energií
íce způsobů, dopracovat
* SD = transformace podle ECOREAV, viz line 571
- lagrange interpolace, kdy se posunou hodnoty o ECOREAV(K) - ECORE(ITXG,ICST)
* Pokud ne Meerge ALL, tak
- AD, AT je stejná interpolace RD a RT, jako výš
* Pokud Merge ALL - merging spectra, see line 620 and below
* sum AD a AT according to the KTYP to RD, RT
sum SD according to the KTYP (to SD)
* 845: (*not XMO/XRS) - xmo: magneto-optics, xrs: resonant-scattering
over ITXG, NE
SUMK(K) = SUMK(K) + 0.5*(E(I)-E(I-1))
*(RD(I,ITXG,K,NPOL)-RD(I,ITXG,K,1))
* Pokud MergeALL, sum RD/RT over K
"""
[docs]
class RATDefinition(OutputFileDefinition):
result_class = RATOutputFile
[docs]
def G(name, **kwargs):
def get(self, option):
return self.option._container.data[name]
return GeneratedValueDefinition(name, get, **kwargs)
_ylabel_base = {
"XAS": r"$\mu(E)$",
"XMCD": r"$\Delta\mu(E)$",
"SPIN": r"$\Delta\mu_{\mathrm{spin\,SR}}(E)$",
"ORBIT": r"$\Delta\mu_{\mathrm{orb\,SR}}(E)$",
}
_kunit_labels = {
1: "Mb",
2: r"$10^{15}\,\mathrm{s}^{-1}$",
}
[docs]
def draw_plot(axis, x, y, title, name=None, kunit=1, edge=None, **kwargs):
axis.set_title(title)
axis.set_xlabel(r"$E-E_{\rm F}$ (eV)")
base = _ylabel_base.get(name, "")
if edge and base:
math = base[1:-1] if base.startswith("$") and base.endswith("$") else base
base = rf"${edge}\;{math}$"
unit = _kunit_labels.get(kunit, "Mb")
axis.set_ylabel(f"{base} ({unit})" if base else f"({unit})")
set_up_common_plot(axis, **kwargs)
axis.plot(x, y)
[docs]
def plot(
option,
multiplot=False,
interpolate_to_fermi=True,
interpolation_threshold=1e-4,
zero_below=True,
zero_below_num=50,
zero_below_energy=25,
no_core_splitting=False,
merge_all=None,
gauss_width=0.0,
lorentz_width=None,
n_valence=None,
core_hole_width="campbell-papp",
mld=False,
*args,
data_generated=False,
**kwargs):
if not data_generated:
option._container.generate_data(
interpolate_to_fermi,
interpolation_threshold,
zero_below,
zero_below_num,
zero_below_energy,
no_core_splitting,
merge_all,
gauss_width,
lorentz_width,
n_valence,
core_hole_width,
mld,
)
def just_plot(plotter, kwargs=kwargs):
for i, d in enumerate(data):
if len(data) > 1:
tit = f"{title} - type {i + 1}"
else:
tit = title
with plotter(title, kwargs) as (axis, kw):
draw_plot(axis, energy, d, tit, name=option.name, kunit=kunit, edge=c.xray_edge, **kw)
c = option._container
kunit = c.GROUPS[0].IFMT()[1]
data = c.data
titles = {
"XAS": "XAS",
"XMCD": data["DICHROISM"],
"SPIN": "XMCD spin sum-rule integrand",
"ORBIT": "XMCD orbital sum-rule integrand",
}
title = f"{titles.get(option.name, option.info)} {c.spectrum_subject}"
energy = data["ENERGY"]
data = data[option.name]
if len(data.shape) == 1:
data = data[None]
if multiplot:
just_plot(multiplot.new_axis, kwargs)
elif len(data.shape) and len(data) > 1:
kwargs["layout"] = (2, 1)
with Multiplot(**kwargs) as mp:
just_plot(mp.new_axis, {})
else:
just_plot(single_plot, kwargs)
[docs]
def nktypes(option):
return len(option._container.core_state_types[0])
[docs]
def create_definition():
definition = create_output_file_definition(
Keyword("RXAS"),
[
G("XAS", plot=(plot, nktypes), info="Polarization averaged X-ray absorption spectra"),
G("XMCD", plot=(plot, nktypes), info="X-ray Magnetic Circular Dichroism"),
G("SPIN", plot=plot, info="XMCD spin sum-rule integrand: 3(\u0394\u03bc\u208a \u2212 2\u0394\u03bc\u208b)"),
G("ORBIT", plot=plot, info="XMCD orbital sum-rule integrand: 2(\u0394\u03bc\u208a + \u0394\u03bc\u208b)"),
G("ENERGY"),
V("NTXRSGRP", int),
V("IDIPOL", int),
V("NPOL", Array(Char(), write_length=True)),
OutputFileSectionDefinition(
"GROUPS",
[
V("SPECTRUM", str),
V("IT", int),
V("NCXRAY", int),
V("LCXRAY", int),
BlankSeparator(),
BlankSeparator(),
SeparatorDefinition(KeywordSeparator("CORE STATES :")),
BlankSeparator(),
V("NCST", int, written_name="NCST:", indent=" "),
V("NKPCOR", Array(int), written_name="NKPCOR:", indent=" "),
BlankSeparator(),
V(
"CORE_STATES",
Table(
{
"ICST": int,
"N": int,
"L": int,
"KAP": int,
"MUE": Array(int, delimiter="/"),
"IKM": int,
"NORM": float,
"E(Ry)": float,
"E(eV)": float,
"<SIGMA_z>": float,
"I0": int,
},
repeat_sparse=["IKM", "NORM"],
),
name_in_grammar=False,
),
BlankSeparator(),
V("N_ENERGIES", int, written_name="number of energies", indent=" "),
V("IFMT", Array(int, length=2), written_name="output format IFMT", indent=" "),
V("VUC", float),
BlankSeparator(),
OutputFileSectionDefinition(
"DATA",
[
V(
"ENERGY",
Sequence(
Complex(postfix=" RYD ", delimiter="", prefix=""), Real(postfix=" EV (REL EF)")
),
name_in_grammar=False,
),
V(
"DATA",
Table(
{"ABSRTA": complex, "ABSRTB": complex, "ABSRATE": complex, "ADD": str},
grouping=True,
numbering=True,
header=False,
groups_as_list=False,
),
name_in_grammar=False,
),
],
name_in_grammar=False,
repeated_delimiter="\n",
is_repeated=BaseDefinition.Repeated.LIST_SECTION,
),
],
is_repeated=True,
name_in_grammar=False,
),
],
name="RAT",
cls=RATDefinition,
info="Result of a X-ray spectroscopy",
)
return definition
definition = create_definition()