Skip to content

Dose calculation

Implementation of dose conversion factors and long/short-term calculations, as well as of an sqlite3 database for storage. The generated sqlite3 database can be explored with the dashboards in ensi_g14_dashboard.

Photon Point-Kernel Comparison

Warning

The photon point-kernel integrator (PKI) is an opt-in, photon-only, advanced comparison model. It is non-regulatory and is not a replacement for the ENSI-G14 calculation. ENSI-G14 remains the reference model.

PKI results are excluded from default totals. They are stored separately with model="pki_photon" and is_reference=False, while ENSI-G14 results retain model="ensi_g14" and is_reference=True. Explicitly request nonreference results when comparing models.

The packaged spectrum includes Cs-137 at 0.6617 MeV with yield 0.8510 photons per disintegration, plus the additional supported nuclides listed in the packaged resource. The packaged attenuation, buildup, and ISO fluence-to-effective-dose data are used without extrapolation. The integrator returns a photon dose coefficient in Sv/Bq.

import numpy as np
from astropy.units import m, s

from ensi_g14_calculator.dose import PointKernelConfig, PointKernelIntegrator


def dispersion_at_points(x, y, z):
    # A production callback normally comes from WeatherAverager.
    return np.ones_like(x.to_value(m)) * (s / m**3)


integrator = PointKernelIntegrator(
    config=PointKernelConfig(max_mean_free_paths=10.0)
)
coefficient = integrator.dose_coefficient(
    "Cs-137",
    (0.0 * m, 0.0 * m, 0.0 * m),
    dispersion_at_points,
)

The input Q to the runner PKI connector is integrated released activity in Bq, and the resulting dose is integrated in Sv. The connector applies parent transport decay and shielding only. Daughter ingrowth and kSPE are omitted. The ISO fluence-to-effective-dose conversion is used.

For runner calculations, global coordinates are east, north, and up relative to the chimney base. Observer height defaults to local terrain, and plume values below terrain are masked. Terrain-intersecting photon rays are not blocked. The first implementation evaluates plume values directly at quadrature points, and the spherical integration is truncated at the configurable maximum number of mean free paths.

Extending Photon Spectra

Advanced users and maintainers can add nuclides to ensi_g14_calculator/data/pki/photon_spectra.csv:

  1. Use the package's canonical nuclide spelling.
  2. Add one row per effective photon line.
  3. Use MeV and photons per disintegration.
  4. Keep every line inside the common attenuation and dose-conversion ranges.
  5. Decide explicitly whether a short-lived daughter line is represented as an effective parent line.
  6. Add a data-repository test for the new nuclide.
  7. Add a runner validation or smoke test if the nuclide uses new energy ranges.

Adding a photon line does not model daughter ingrowth. Custom normalized tables can be supplied through the public PkiDataRepository constructor.

ensi_g14_calculator.dose

Dose calculation APIs, including the opt-in photon point-kernel model.

Classes

PointKernelConfig dataclass

Numerical settings for spherical point-kernel quadrature.

PointKernelIntegrator

Integrate physical photon dispersion around one observer.

Functions
line_dose_coefficient(line, observer_xyz, dispersion_evaluator)

Return one photon-line coefficient in Sv / Bq.

This is useful for training labels, where energy is sampled continuously and therefore is not present in the packaged nuclide spectrum table. line must be a validated PhotonLine-like object containing an energy and a photon yield.

dose_coefficient(nuclide, observer_xyz, dispersion_evaluator)

Return the integrated photon PKI dose coefficient in Sv / Bq.

dose_coefficient_many(nuclide, observer_xyz, dispersion_evaluator, observer_batch_size=8)

Return PKI coefficients for many observers using batched quadrature.

observer_xyz may be a quantity with shape (n, 3) or a tuple of three broadcast-compatible length arrays. The observer coordinates may be anywhere around the chimney, including upwind positions. The dispersion callback receives source-volume cloud points, not observer coordinates, and is called once per photon line and observer batch.

PkiDataRepository

Load AND interpolate the normalised photon point-kernel data tables.

PkiSubmersionSurrogate

Vectorized equivalent-dispersion inference for one Gaussian component.

Functions
equivalent_dispersion(downwind, crosswind, vertical_separation, p_y, q_y, p_z, q_z, energy, velocity)

Return equivalent dispersion in s/m^3 with broadcast input shape.

semi_infinite_line_coefficient(energy, maximum_mean_free_paths=40.0, radial_order=128)

Return the unit-yield half-space reference coefficient in Sv m3.

SurrogateDomainError

Bases: ValueError

Raised when an input lies outside the trained surrogate domain.

Modules

dose_database

Classes
DoseDatabase
Functions
insert_chimney(chimney_id, latitude, longitude, settings=None)

Parameters:

Name Type Description Default
settings dict

optional {column: value} for the settings columns of the chimneys table, e.g. {'decay_in_plume': 1, 'near_field': 0}; missing columns are NULL (not recorded).

None
insert_transport_factors_bulk(records)

Insert from existing dict directly. records: {(chimney, group, nuclide): {'avg_chi_k': , ...}}

insert_dose(chimney, group, nuclide, pathway, dose_by_age_group, model='ensi_g14', is_reference=True, extensions=None)

Parameters:

Name Type Description Default
extensions

iterable of tags of EXTENSIONS in effect ([] for strict G-14), or None (not recorded).

None
format_extensions(extensions) classmethod

Tags -> sorted comma-separated string ('' for strict G-14); None stays None (not recorded).

insert_doses_for_case(chimney, group, nuclide, doses, model='ensi_g14', is_reference=True, extensions=None)

doses: {'direct': {dict of dose by age group}, 'dairy': {dict of dose by age group}, ...}

get_total_dose(group, age_group, model=None, include_non_reference=False)

Total dose (all chimneys, all nuclides, all pathways) for a group.

get_dose_by_pathway(group, age_group, model=None, include_non_reference=False)

Total dose per pathway (summed over chimneys and nuclides) for a group.

get_dose_by_nuclide(group, age_group, model=None, include_non_reference=False)

Total dose per nuclide (summed over chimneys and pathways) for a group.

get_dose_by_chimney(group, age_group, model=None, include_non_reference=False)

Total dose per chimney (summed over nuclides and pathways) for a group.

get_dose_breakdown(group, age_group, chimney=None, nuclide=None, pathway=None, model=None, include_non_reference=False)

Flexible breakdown: filter by any combination of chimney, nuclide, pathway. Returns one row per (chimney, nuclide, pathway).

dose_template_class

base class that

  • loads the numerous dose coefficients from our parsed CSV / yaml files
  • implements formulae that are identical for long term or short term doses.
Classes
DoseTemplate

always calculates for 3 groups: toddler, child, adult.

Attributes
scenario_type property

to be defined by child classes to return 'long_term' or 'short_term' or maybe 'potential'.

Functions
decay_constant(nuclide)

Decay constant of a nuclide (Table 4.1), with units (1/year).

has_daughters(nuclide)

Whether the nuclide has daughter products in Table 4.3.1.

decay_chain(nuclide)

Decay constants of a nuclide and of its daughters (Table 4.1 and Table 4.3.1).

Parameters:

Name Type Description Default
nuclide

string, e.g. 'Kr-88'

required

Returns:

Type Description

tuple (lambda_m, [(daughter name, lambda_d), ...]), decay constants with units (1/year)

integral_exp(lambda_rate, time) staticmethod

Used a lot in those formulae, (1-exp(lambda_rate * time))/ lambda_rate

Parameters:

Name Type Description Default
lambda_rate

unit 1 / time

required
time

unit time

required

Returns:

Type Description

unit time

submersion(nuclide, Q, chi_KL_corrected, T_fz=None, k_s=None)

Calculates E_sub (External radiation from plume), appendix G14 A3.1.1. Same formula for all cases (long, short, accident).

Parameters:

Name Type Description Default
nuclide

string, e.g., 'Be-7'

required
Q

dose in Bq, yearly for long term, or scenario-dependent for short term.

required
chi_KL_corrected

short or long term dispersion coefficient, the "corrected" version. Or a DecayedDispersion (decay during transport inside the plume), then no T_fz.

required
T_fz

time of flight of the nuclide from emission point to point of interest. if None, using the Richtlinie provided value

None
k_s

shieding factor, default None. if None, Richtlinie value used. Pass k_s=1 for short term scenarios, when <8h, since no shielding is to be considered then.

None

Returns:

Type Description

dictionary {'toddler_1_year': dose_Sv, 'child_10_year': dose_Sv, 'adult': dose_Sv}

submersion_pki(nuclide, Q, dose_coefficient, T_fz=None, k_s=None)

Calculate photon-only point-kernel submersion dose.

dose_coefficient is an integrated geometrical/spectral coefficient in Sv / Bq. This connector applies only parent decay and shielding; it does not use the regulatory infinite-cloud coefficient, K_C, kSPE, or daughter-product terms. It is calculated with the point_kernel.py class, integrating over a gaussian plume. see examples in short-term and long-term scenario runners. If the coefficient was integrated over a cloud with decay during transport (time_weight of the averager, see plume_decay.py), pass T_fz=0 * year so that the parent is not decayed twice.

inhalation(nuclide, Q, chi_KL, T_fz=None, increased_breathing_rate_for_adults=False)

Calculates E_inh (Inhalation dose), A3.2.1. Same formula for all cases. Resuspension must be added for accidents.

Parameters:

Name Type Description Default
nuclide

string, e.g., 'Be-7'

required
Q

dose in Bq, yearly for long term, or scenario-dependent for short term.

required
chi_KL

short term dispersion coeff chi_K, or long term version coefficient chi_L. Or a DecayedDispersion (decay during transport inside the plume), then no T_fz.

required
T_fz

time of flight of the nuclide from emission point to point of interest.

None
increased_breathing_rate_for_adults

toggles higher breathing rate for adults, to be used in first 8h of accident scenarios.

False

Returns:

Type Description

dictionary {'toddler_1_year': dose_Sv, 'child_10_year': dose_Sv, 'adult': dose_Sv}

ingestion_tritium_from_air(Q_HTO, chi_k, W, I_N)

Calculates formula in A3.6.1. Same formula for all cases (short-, long- and accident). Confirmed with Andreas Leupin and Mohsen Meshkian from ENSI on 2026-05-18: this formula should indeed also be used for short term.

Parameters:

Name Type Description Default
Q_HTO Quantity

Quantity, Bq, yearly for long term, or scenario-dependent for short term.

required
chi_k Quantity

dispersion, Quantity, must be cast-able to s/m^3

required
W Quantity

washout factor, Quantity, must be cast-able to 1/m^2

required
I_N Quantity

Quantity, must be cast-able to m. Rain in summer half rain (long term) OR amout of rain during release event (short term)

required

Returns:

Type Description

dictionary of doses, first key pathway (meat, dairy, plant ...), second key exposed group. dictionary structure different from other dose methods because we have 3 pathways here!

ingestion_C14(Q, chi)

Calculates formula in A3.5.1. Same formula for long term and short term. FOR SHORT TERM: MULTIPLY BY 2 per text. The short term runner of this package takes care of this 2 fold factor.

Parameters:

Name Type Description Default
Q

average yearly emission in Bq for long term, or scenario-dependent for short term.

required
chi

dispersion parameter in s/m^3

required

Returns:

Type Description

dictionary of dose by exposued group and pathway

ingestion_from_water(nuclide, Q, J)

Calculates formula in A.7.1

Parameters:

Name Type Description Default
nuclide str

string, e.g. 'Cs-137'.

required
Q Quantity

average yearly emission in Bq for long term, or scenario-dependent for short term.

required
J Quantity

flow rate of the river in m^3/s. (very nuclear-plant centric)

required

Returns:

Type Description

dictionary of dose by pathway and by exposed group.

ingestion_from_water_tritium(Q, J)

Calculates formula in A.8.1

Parameters:

Name Type Description Default
Q

average yearly emission in Bq for long term, or scenario-dependent for short term.

required
J

flow rate of the river in m^3/s. (very nuclear-plant centric)

required

Returns:

Type Description

dictionary of dose by exposure pathway and by exposed group.

Functions

long_term_dose

Classes
LongTermDoseCalculator

Bases: DoseTemplate

Functions
ground_shine(nuclide, Q, Q_current_year, xi_L, a_f_0, a_s_0)

Calculates E_BS (Ground radiation for calendar year), appendix A3.3.1.2 This cumulates both legacy activity (a_0) and current year deposition.

Parameters:

Name Type Description Default
nuclide

string, nuclide, e.g. Be-7

required
Q

usual yearly activity release in Bq (for nuclear plants: "100-year average")

required
Q_current_year

activity released in the present year. For us (non-nuclear-plant facilities), we can probably set Q_current_year = Q.

required
xi_L

long term deposition factor xi_L, in 1/m^2

required
a_f_0

activity in soil, fast component, obtained from the activity_inventory class.

required
a_s_0

activity in soil, slow component, obtained from the activity_inventory class.

required

Returns:

Type Description

dictionary, one entry per age group, in Sv (yearly dose)

ingestion_plant(nuclide, Q, Q_Vj, xi_p, xi, C_bo_0)

implementation of appendix A3.4.1.2.

Parameters:

Name Type Description Default
nuclide

string

required
Q

emission this year (for leaves), in Bq

required
Q_Vj

average yearly emission in Bq

required
xi_p

deposition on plants, 1/m^2

required
xi

deposition on soil, 1/m^2

required
C_bo_0

steady state soil concentration, Bq/kg

required

Returns:

Type Description

dictionary, one entry per age group, in Sv (yearly dose)

ingestion_milk_dairy(nuclide, Q, Q_Vj, xi_p, xi, C_bo_0)

implementation of appendix A3.4.2.2

Parameters:

Name Type Description Default
nuclide

string

required
Q

emission this year (for leaves), in Bq

required
Q_Vj

average yearly emission in Bq

required
xi_p

deposition on plants, 1/m^2

required
xi

deposition on soil, 1/m^2

required
C_bo_0

steady state soil concentration, Bq/kg

required

Returns:

Type Description

dictionary, one entry per age group, in Sv (yearly dose)

ingestion_meat_eggs(nuclide, Q, Q_Vj, xi_p, xi, C_bo_0)

implementation of appendix A3.4.3.2

Parameters:

Name Type Description Default
nuclide

string

required
Q

emission this year (for leaves), in Bq

required
Q_Vj

average yearly emission in Bq

required
xi_p

deposition on plants, 1/m^2

required
xi

deposition on soil, 1/m^2

required
C_bo_0

steady state soil concentration, Bq/kg

required

Returns:

Type Description

dictionary, one entry per age group, in Sv (yearly dose)

Functions

point_kernel

Spherical point-kernel quadrature for photon submersion dose.

Classes
PointKernelConfig dataclass

Numerical settings for spherical point-kernel quadrature.

PointKernelIntegrator

Integrate physical photon dispersion around one observer.

Functions
line_dose_coefficient(line, observer_xyz, dispersion_evaluator)

Return one photon-line coefficient in Sv / Bq.

This is useful for training labels, where energy is sampled continuously and therefore is not present in the packaged nuclide spectrum table. line must be a validated PhotonLine-like object containing an energy and a photon yield.

dose_coefficient(nuclide, observer_xyz, dispersion_evaluator)

Return the integrated photon PKI dose coefficient in Sv / Bq.

dose_coefficient_many(nuclide, observer_xyz, dispersion_evaluator, observer_batch_size=8)

Return PKI coefficients for many observers using batched quadrature.

observer_xyz may be a quantity with shape (n, 3) or a tuple of three broadcast-compatible length arrays. The observer coordinates may be anywhere around the chimney, including upwind positions. The dispersion callback receives source-volume cloud points, not observer coordinates, and is called once per photon line and observer batch.

point_kernel_data

Loading and validating data for the photon point-kernel integration metohd for submersion dose.

Classes
PhotonSpectrumNotFound

Bases: LookupError

Raised when no packaged photon spectrum exists for a nuclide.

PhotonLine dataclass

One photon line, with yield expressed per parent disintegration.

PkiDataRepository

Load AND interpolate the normalised photon point-kernel data tables.

short_term_dose

Classes
ShortTermDoseCalculator

Bases: DoseTemplate

Inherits from the template, which does the loading of the parameters and implements some of the dose formulae (those common to all scenarios).

We always need some "total" deposited dose, \(\bar{Q \xi}\) and \(\bar{Q \xi_p}\). These depend on the scenario, so I wrote small classes (PotentialExposureScenario and ShortTermScenario) to handle them -- just for safety.

So, unlike the long term dose class where we just pass a yearly activity "Q", here we will pass an instance of a Scenario, which will compute its own \(\bar{Q \xi}\) quantities.

Functions
resuspension(nuclide, scenario, T_exp)

Second formula of Section A3.2.1; to be considered in scenarios of accidents. T_exp must be exactly 1 year or 50 years.

Parameters:

Name Type Description Default
nuclide

str

required
scenario Scenario

scenario object (from ensi_g14_calculator.short_term_scenarios.release_by_time_interval)

required
T_exp

Quantity, 1 year or 50 years

required

Returns:

Type Description

a dose in Sv.

ground_shine(nuclide, scenario, T_exp)

Calculates E_BS (ground radiation for T_exp years) section A3.3.1.1.

Parameters:

Name Type Description Default
nuclide

str

required
scenario Scenario

scenario object (from ensi_g14_calculator.short_term_scenarios.release_by_time_interval)

required
T_exp

Quantity, 1 year or 50 years

required

Returns:

Type Description

a dose in Sv.

ingestion_plant(nuclide, scenario, T_exp, release_date, f_cont=None)

Calculates ingestion dose from plants, depending on T_exp, section A3.4.1.1.

Parameters:

Name Type Description Default
nuclide

str

required
scenario Scenario

scenario object (from ensi_g14_calculator.short_term_scenarios.release_by_time_interval)

required
T_exp

Quantity, years. Unlike ground_shine and resuspension, T_exp is not limited to 1 or 50 years only.

required
release_date

str, release date in 'mm-dd' format (e.g., '06-15' for June 15).

required
f_cont

Default None. Share of the produce contaminated within a 5km radius. Can be averaged (through scenario, average \bar{Q \xi} on a 5km radius disk centred on the facility, then pass fcont=1. If not given, then the max was taken (>500m), and fcont is read from the Richtlinie parameters.

None

Returns:

Type Description

a dose in Sv.

ingestion_milk_dairy(nuclide, scenario, T_exp, release_date)

Calculates ingestion dose from milk and dairy depending on T_exp, section A3.4.2.1.

Parameters:

Name Type Description Default
nuclide

str

required
scenario Scenario

scenario object (from ensi_g14_calculator.short_term_scenarios.release_by_time_interval)

required
T_exp

Quantity, years. Unlike ground_shine and resuspension, T_exp is not limited to 1 or 50 years only.

required
release_date

str, release date in 'mm-dd' format (e.g., '06-15' for June 15).

required

Returns:

Type Description

a dose in Sv.

ingestion_meat_eggs(nuclide, scenario, T_exp, release_date)

Calculates ingestion dose from milk and dairy depending on T_exp, section A3.4.3.1.

Parameters:

Name Type Description Default
nuclide

str

required
scenario Scenario

scenario object (from ensi_g14_calculator.short_term_scenarios.release_by_time_interval)

required
T_exp

Quantity, years. Unlike ground_shine and resuspension, T_exp is not limited to 1 or 50 years only.

required
release_date

str, release date in 'mm-dd' format (e.g., '06-15' for June 15).

required

Returns:

Type Description

a dose in Sv.

Functions

submersion_surrogate

NumPy-only inference for the optional local-flat PKI surrogate.

Classes
SurrogateDomainError

Bases: ValueError

Raised when an input lies outside the trained surrogate domain.

PkiSubmersionSurrogate

Vectorized equivalent-dispersion inference for one Gaussian component.

Functions
equivalent_dispersion(downwind, crosswind, vertical_separation, p_y, q_y, p_z, q_z, energy, velocity)

Return equivalent dispersion in s/m^3 with broadcast input shape.

semi_infinite_line_coefficient(energy, maximum_mean_free_paths=40.0, radial_order=128)

Return the unit-yield half-space reference coefficient in Sv m3.