Nucleide

Clearance Screening

The clearance index CI = sum of A_i/CL_i fractions, the sum-of-fractions release rule, the Sublet S1–S7 radiological totals (activity, decay heat, committed hazards, transport ratio, IAEA clearance index, gamma dose), classification against the vendored EU Annex VII default table, and the inventory readers that feed them.

Activation codes such as ALARA and FISPACT-II answer “how radioactive is this material after irradiation and cooling?”. Clearance screening asks the follow-up regulatory question: “is it low enough to be released from regulatory control?”. Nucleide implements the screening arithmetic — the clearance index and the sum-of-fractions rule — as pure arithmetic over parsed activation inventories, with the EU clearance table vendored as an attributed default. This is screening arithmetic, never a compliance decision: real clearance requires the governing regulatory table, material bookkeeping, and the national transposition of the underlying directive.

Overview

A clearance table lists, per radionuclide, an activity concentration below which material may be cleared from regulatory control. Given an inventory of per-nuclide activities A_i and matching per-nuclide clearance levels CL_i, each nuclide contributes the fraction A_i / CL_i of its limit, and the material satisfies the screening criterion when the sum of those fractions does not exceed 1 — the sum-of-fractions rule of IAEA Safety Standards Series No. RS-G-1.7 §5, referenced here by designation only (the IAEA tables themselves are never vendored). The methodology follows Sublet et al.sublet-2017

Nucleide keeps the arithmetic table-driven and the inputs pluggable:

  • Limit tables are caller-supplied key/value maps, or the vendored EU 2013/59/Euratom Annex VII Table A transcription used as the attributed defaulteu-2013-59-euratom.
  • Inventories come from parsed activation output — ALARA response-frame rows (Rust inventory_from_frame), or any other source supplying the same (nuclide, activity) pairs — and from the FISPACT-II wide inventory table printed with the HAZARDS + CLEAR keywords, whose per-nuclide clearance index column Nucleide can read back verbatim.
  • Classification returns the fraction sum, a satisfied/exceeded class, and the dominant contributor — the single nuclide carrying the largest fraction.
Code shape

The screening arithmetic lives in two clearance modules: nucleide-alara-io (the limit table, the screening functions, and inventory extraction from parsed response frames) and nucleide-fispact-io (the reader for the wide FISPACT-II inventory table), plus the sublet module (nucleide-alara-io) for the S1–S7 radiological totals below. The Python surface is thin: nucleide.alara re-exports the clearance helpers (alara_clearance_index, alara_sum_of_fractions, alara_clearance_eu_table) and the totals (alara_total_activity, alara_decay_heat, alara_ingestion_hazard, alara_inhalation_hazard, alara_transport_ratio, alara_iaea_clearance_index, alara_dose_slab, alara_dose_point, alara_dose_mixture_mu), and nucleide.fispact re-exports the clearance-block reader (fispact_parse_clearance).

Mathematical details

Clearance index

For an inventory of N nuclides with activities A_i and clearance levels CL_i on a common unit basis, the per-nuclide fraction and the clearance index are

fi=AiCLi,CI=∑i=1Nfi=∑i=1NAiCLi.(CI)f_i = \frac{A_i}{CL_i}, \qquad \mathrm{CI} = \sum_{i=1}^{N} f_i = \sum_{i=1}^{N} \frac{A_i}{CL_i}. \tag{CI}

Sum-of-fractions rule

The screening classification places the boundary at CI = 1, boundary included — matching the exemption/clearance wording “does not exceed”:

CI≤1  ⟺  screening satisfied,CI>1  ⟺  screening exceeded.(SOF)\mathrm{CI} \le 1 \;\Longleftrightarrow\; \text{screening satisfied}, \qquad \mathrm{CI} > 1 \;\Longleftrightarrow\; \text{screening exceeded}. \tag{SOF}

The same pass reports the dominant contributor, the nuclide whose f_i is the largest single fraction (useful for finding what to wait out or clean up); an empty inventory gives CI = 0 with no dominant nuclide.

The vendored EU default table

ClearanceTable::eu_annex_vii (Rust) / alara_clearance_eu_table (Python) returns a transcription of Council Directive 2013/59/Euratom Annex VII Table A — “activity concentrations and activities per unit mass of radionuclides … for solid materials” — in Bq/g (numerically identical to the directive’s kBq/kg)eu-2013-59-euratom. The transcription holds 260 entries: Part 1 radionuclide names verbatim (minus equilibrium footnote markers, isomer letter kept), plus the Part 2 U-238/Th-232 decay chains transcribed at the parent level with K-40, since the chain values apply to each member (directive Annex VII 2(b)–(d)). It was transcribed from the official legal text (EUR-Lex CELEX:32013L0059, OJ L 13, 17.1.2014, accessed 2026-09-15) and is reusable with attribution per Commission Implementing Decision (EU) 2011/833. Spot values used by the correctness checks:

NuclideCL (Bq/g)
H-3100
C-141
Co-600.1
Cs-1370.1
Sr-901
Pu-2390.1
U-2381
Th-2321
K-4010

The Spanish CSN conditional NORM tables

ClearanceTable::es_conditional_inert/non_hazardous/hazardous (Rust) / alara_clearance_es_table (Python) serve the Spanish Consejo de Seguridad Nuclear draft technical opinion CSN/PDT/AICD/TGE/2503/02 (TGE/VAR/2025/1, hosted on csn.es, accessed 2026-09-16 — the RD 1029/2022 and RD 1217/2024 transposition of Directive 2013/59/Euratom): conditional clearance levels in Bq/g for NORM landfill disposal, one table per landfill type (Tablas 1–3: inert, non-hazardous, hazardous) with one column per NORM material nature (rocks, ashes, sands, slags, oil & gas). The Tabla 4 natural decay-chain keys expand to per-member entries at the parent value through the shared dialect machinery (the EU Part-2 precedent above), with the last claiming key supplying a shared member’s level. The caller selects the table and the material column explicitly — there is no cross-table logic and no “most permissive wins”; the EU Annex VII table stays the default. Screening arithmetic only, never a compliance decision. Spot values used by the correctness checks (C6: transcription spots, chain-expansion members, and a hand-computed screening vector at exact equality):

Table / columnNuclideCL (Bq/g)
Inert, rocksU-23810
Inert, rocksRa-22610
Inert, rocksPo-2105
Inert, rocksTh-2325
Inert, rocksK-4010
Hazardous, oil & gasU-238500
Hazardous, oil & gasPo-210500
Hazardous, oil & gasK-40500

The FISPACT-II clearance column

In the wide HAZARDS + CLEAR inventory table, FISPACT-II prints a per-nuclide clearance-index column computed code-side as the activity divided by the code’s own internal clearance-library value — not against the EU table. Nucleide’s reader carries that column verbatim (and can total it), so code-printed and Nucleide-recomputed indices stay distinguishable; the screening functions themselves always recompute f_i against the caller’s table.

Sublet S1–S7 radiological totals

Inventories also feed seven radiological totals, pinned to the open CCFE-PR(16)53 author preprint of the FISPACT-II system paper — Tables X (inventory/power row definitions), VI (decay-type identifiers), VII (radiation types), and XVII (validation anchors) — and the open FISPACT-II output_interpretation reference, never the paywalled journal pages:

Ai=Ni λi [Bq],Atot=∑iAi,(S1)A_i = N_i\,\lambda_i\ \mathrm{[Bq]}, \qquad A_\mathrm{tot} = \sum_i A_i, \tag{S1}

with the FISPACT-II IRT α/β/γ split from the output_interpretation activity-breakdown section: IRT 4 → alpha; IRT 1, 2, 11, 14, 16, 17, 19, 20 → beta; IRT 3 → gamma; IRT 12, 13 split α/β and IRT 15 splits α/γ by caller-supplied branch fractions (IRT classes are Table VI, identifiers 1–27 with 8, 9 unused and 10 unknown; unmapped IRTs are a clear error). Decay heat folds caller-supplied average decay energies into per-class power:

Hc=∑iAi Ec,i C1 [kW],C1=1.602176634×10−22 kJ/eV,(S2)H_c = \sum_i A_i\,E_{c,i}\,C_1\ \mathrm{[kW]}, \qquad C_1 = 1.602176634\times 10^{-22}\ \mathrm{kJ/eV}, \tag{S2}

the Table X Ai·E·C1 rows (radiation classes are Table VII, STYP 0–9). Both totals carry the excluding-tritium companion (total minus the tritium entry), and parts sum to the total exactly by construction. Decay energies stay caller-supplied, never vendored.

The ingestion and inhalation hazards fold the same Table X row with caller-supplied 50-year committed dose coefficients (Sv/Bq, never vendored — the ICRP tables are copyrighted) into the wide-table dose columns 8/9 and their HAZARDS totals, each with the excluding-tritium companion:

Ding=∑iAi eiing [Sv],Dinh=∑iAi eiinh [Sv].(S4–S5)D_\mathrm{ing} = \sum_i A_i\,e^\mathrm{ing}_i\ \mathrm{[Sv]}, \qquad D_\mathrm{inh} = \sum_i A_i\,e^\mathrm{inh}_i\ \mathrm{[Sv]}. \tag{S4–S5}

The transport ratio folds the Table X row with caller A2 limits (TBq, never vendored) through Eqs. 78/79 into the ATWO-block total, with the effective A2 defined by it exactly:

R=∑iAiA2,i C2,hA2=∑iAiR [TBq],C2=1012 TBq→Bq.(S6)R = \sum_i \frac{A_i}{A_{2,i}\,C_2}, \qquad h_{A2} = \frac{\sum_i A_i}{R}\ \mathrm{[TBq]}, \qquad C_2 = 10^{12}\ \mathrm{TBq{\to}Bq}. \tag{S6}

The IAEA clearance-index variant folds the Table X row with caller IAEA levels (Bq/kg, never vendored — the IAEA tables are permission-gated) and the total mass through Eq. 80 into the CLEAR index, screened at the landed <= 1 boundary exactly like the EU-table variant above:

CIIAEA=∑iAiMtot Li,(S7)\mathrm{CI}_\mathrm{IAEA} = \sum_i \frac{A_i}{M_\mathrm{tot}\,L_i}, \tag{S7}

dimensionless, with the dominant per-nuclide term reported alongside.

The gamma dose rate sums over energy groups rather than nuclides, with the DOSE-page buildup B=2B = 2 and conversion C=3.6×109 ∣e∣C = 3.6\times 10^9\,|e| (MeV/kg/s → Sv/h):

Dslab=C B2∑gμa(Eg)μm(Eg) Sγ(Eg),Sγ(Eg)=Ig A(t),(S3a)D_\mathrm{slab} = C\,\frac{B}{2}\sum_g \frac{\mu_a(E_g)}{\mu_m(E_g)}\,S_\gamma(E_g), \qquad S_\gamma(E_g) = I_g\,A(t), \tag{S3a} Dpoint=C∑gμa(Eg)4πr2 e−μ(Eg) r ms Sγ(Eg),(S3b)D_\mathrm{point} = C\sum_g \frac{\mu_a(E_g)}{4\pi r^2}\, e^{-\mu(E_g)\,r}\,m_s\,S_\gamma(E_g), \tag{S3b}

with the mixture rule μm=∑jfj μm,j\mu_m = \sum_j f_j\,\mu_{m,j}. Every attenuation input stays caller-supplied; distances below 0.3 m clamp loudly, never silently. Bremsstrahlung, DPA/KERMA/gas, and fission counts stay out. Gates are hand-computed vectors at exact equality (Table XVII copper total-activity anchor) plus always-run C7–C10 gates in the same clearance validation report.

Algorithm

  • Table construction. ClearanceTable is a canonical-NuclideId-keyed map. insert validates that each limit is finite and strictly positive and raises a clear error otherwise. The EU default is embedded as a TSV at build time, parsed lazily on first call, and its invariants are checked at load — since it is committed data, a corrupt transcription fails loudly rather than returning a partial table. Nuclide keys resolve through the shared dialect machinery, so Co60, Co-60, and co-60 are one key, not three.
  • Screening accumulation. One pass over the inventory: validate each activity (finite, >= 0), require a table entry for every inventory nuclide (a missing limit raises a clear error — never a silent skip), accumulate f_i into the sum, and track the running maximum fraction and its nuclide. The class falls out of (SOF) with the boundary included.
  • ALARA frame inventories. inventory_from_frame keeps the SpecificActivity rows at one cooling time and drops the total aggregate row, yielding (nuclide, activity) pairs in the frame’s own activity unit (ALARA prints Bq/cm³, FISPACT-II prints Bq) — the caller pairs them with a table on the matching basis.
  • FISPACT-II wide-table reader. parse_clearance recognizes TIME INTERVAL <n> headers with their TIME IS / COOLING TIME IS step tags (the first <value> <unit> pair gives the step time in seconds, with s m h d w y names accepted and y = 365 d), then the two-line NUCLIDE … CLEARANCE … HALF LIFE column header, then data rows of <symbol> <mass>[isomer] [flags] <11 floats> <half-life|Stable>. Flags (# stable, > present before irradiation, & gamma spectrum approximate, ? convergence not reached) are kept verbatim, Stable is stored as -1.0 seconds (matching the ALARA output convention), and nuclide names are normalized to the shared dialect spelling (V 55 → V-55, Rb 86m → Rb-86m). The table ends at TOTAL NUMBER OF NUCLIDES PRINTED IN INVENTORY or the first non-data line; blank input and every malformed row or header raise a clear, line-numbered parse error, while a file with no clearance table at all yields an empty scan for the caller to judge.

Worked example

Hand arithmetic. Take the vendored EU defaults, CL_Co = 0.1 Bq/g and CL_H = 100 Bq/g, and a massic inventory A_Co = 0.05 Bq/g, A_H = 25 Bq/g. Then f_Co = 0.05/0.1 = 0.5, f_H = 25/100 = 0.25, and CI = 0.75 ≤ 1 — satisfied, with Co-60 the dominant contributor at half the limit on its own.

Worked example as fractions: 0.05/0.1 + 25/100 = 0.75 ≤ 1 — satisfied, Co-60 dominant.

Real API, end to end. The same screen over a parsed FISPACT-II clearance table (the synthetic fixture fixtures/fispact/clearance.out, interval 1: stable V-55 and Fe-56 at zero activity, Co-60 at 4.0e6 Bq, Rb-86m at 8.0e9 Bq) against caller limits {V-55: 1, Fe-56: 1, Co-60: 4.0e6, Rb-86m: 8.0e9} gives f = 0 + 0 + 1 + 1 = 2.0 > 1 — exceeded:

from pathlib import Path

import nucleide.alara as alara
import nucleide.fispact as fispact


out = alara.alara_sum_of_fractions({"Co-60": 0.05, "H-3": 25.0})
print(out)

# Same screen over a parsed FISPACT-II clearance table.
rows = fispact.fispact_parse_clearance(Path("fixtures/fispact/clearance.out").read_text())
step1 = {r["nuclide"]: r["activity_bq"] for r in rows if r["interval"] == 1}
print(alara.alara_sum_of_fractions(step1, {"V-55": 1.0, "Fe-56": 1.0,
                                           "Co-60": 4.0e6, "Rb-86m": 8.0e9}))
{'class': 'satisfied', 'max_fraction': 0.5, 'max_nuclide': 'Co-60', 'sum': 0.75}
{'class': 'exceeded', 'max_fraction': 1.0, 'max_nuclide': 'Co-60', 'sum': 2.0}

Both vectors are hand-computable exactly, and the sum-of-fractions boundary is probed on both sides in the test suite: CI = 1 classifies as satisfied, one float-ulp above as exceeded.

Assumptions and limitations

  • Screening arithmetic, not a compliance decision. Clearance in practice requires the governing regulatory table, complete material bookkeeping, and the national transposition of the underlying directive.
  • Unit basis is the caller’s discipline. A_i and CL_i must share one basis. The vendored EU table is an activity-concentration table (Bq/g), so inventories screened against it must be massic activities (Bq/g), not total activities (Bq). Frame extraction returns activities in the frame’s own unit (ALARA Bq/cm³ vs FISPACT-II Bq), so the table basis must be chosen to match.
  • Complete tables, valid values. Every inventory nuclide must have a table entry; a missing limit raises a clear error. Negative or non-finite activities, and non-positive or non-finite limits, raise a clear error — nothing is skipped silently. Zero activities contribute nothing.
  • Duplicate nuclides in a caller-built inventory (Rust slice input) sum like any other pair — deduplication is the caller’s job; Python dict keys are unique by construction.
  • The FISPACT-II clearance column is code-side. Its per-row values are the code’s internal library ratios, not EU-table fractions, and are read back as printed; Nucleide screening always recomputes against the caller’s table.
  • Further indices stay recorded, not implemented. Bremsstrahlung, DPA/KERMA/gas, and fission counts appear as columns in the wide table and are parsed where present, but only the clearance screen plus the S1–S7 totals above are implemented as metricssublet-2017.
  • S1–S7 input discipline. Per-nuclide activities and decay energies are caller data (finite, non-negative); the α branch fraction is required for the split IRTs 12, 13, 15 and rejected elsewhere; IRTs with no pinned mapping raise a clear error, never a silent bucket. Dose coefficients, A2 limits, IAEA levels, and attenuation tables are caller data too (finite, non-negative; limits/levels strictly positive; mixture fractions summing to 1); the total mass is strictly positive and the slab ratio divides by a strictly positive mixture coefficient.
  • Time and stability conventions. FISPACT-II step times convert with y = 365 d; Stable half-lives store as -1.0.
  • Correctness checks. The arithmetic is pinned by hand-computed vectors at exact equality (CI values, boundary probes both sides, EU and ES spot values, ES chain-expansion members) and by an end-to-end parse of the synthetic fixture in the real FISPACT-II grammar, in tests/test_clearance.py and validation/clearance_vs_pypact.py. The S1–S7 totals are pinned the same way: hand-computed activity/heat/hazard/ratio/ index/dose vectors at exact equality (parts-to-total conservation, ex-tritium companions, split-IRT fractions, the =1 boundaries, the loud 0.3 m clamp, loud-error vectors) plus the always-run C7–C10 gates in the same report.

The independent comparison for the reader is pypact, the upstream Apache-2.0 FISPACT-II output reader on PyPIpypact: this repository’s validation harness re-parses the synthetic clearance fixture with pypact’s InventoryReader and compares the overlapping per-nuclide columns (atoms, activity, clearance index) — agreement is exact (zero relative error) over the fixture’s seven nuclide-steps, and the check reports a clearly-stated skip when pypact is not installed. Adjacent capabilities Nucleide does not implement: the rest of Sublet et al.’s radiological index suite as first-class metrics, and any vending of the IAEA clearance/exemption tables (RS-G-1.7, GSG-17), which stay designation-only references.