Skip to content

chain.replace_missing walks off the end of ATOMIC_SYMBOL and raises KeyError: -1 #22

Description

@shimwell

replace_missing looks for a decay-data stand-in by stepping the atomic number towards stability until it finds a nuclide the library has:

while product not in decay_data:
    if Z > 98:
        Z -= 2
        A -= 4
    else:
        if beta_minus:
            Z += 1
        else:
            Z -= 1
    product = f'{ATOMIC_SYMBOL[Z]}{A}'

There is nothing stopping the walk. If the direction it picks never reaches a nuclide in decay_data, Z runs down past 1 and the loop dies on ATOMIC_SYMBOL[-1]:

>>> import endf
>>> from endf.chain import Chain
>>> names = ['dec-049_In_115', 'dec-049_In_116', 'dec-050_Sn_116']
>>> decay = [endf.Material(f'tests/{n}.endf') for n in names]
>>> neutron = [endf.Material('tests/n-049_In-115_trimmed.endf')]
>>> Chain.from_endf(decay, [], neutron, reactions=('(n,gamma)',), progress=False)
missing In116 ec/beta+ Cd116
...
  File "endf/chain.py", line 209, in replace_missing
    product = f'{ATOMIC_SYMBOL[Z]}{A}'
                 ~~~~~~~~~~~~~^^^
KeyError: -1

In116 has a small ec/beta+ branch to Cd116. With no cadmium in decay_data the direction is judged from mass_longest_lived, which defaults to the product's own A, so beta_minus is False and the walk goes down: Ag, Pd, Rh … and off the end. The stand-in it wanted was one step up, at In116.

A full decay sub-library has every nuclide, so the walk always terminates within a step or two and this never shows. It bites as soon as the library is a subset — a reduced chain, a test fixture, or a library that simply lacks an element.

The direction heuristic is reasonable and I would not change it. What is missing is a bound:

while product not in decay_data:
    ...
    if not 1 <= Z <= 118 or A < 1:
        return None   # or raise a message naming the product
    product = f'{ATOMIC_SYMBOL[Z]}{A}'

and then a caller that copes with Nonefrom_endf already handles None from the Z == 0 neutron case a few lines above, so most of the plumbing is there. Reporting the product by name would also be more useful than KeyError: -1, which says nothing about what was being replaced.

Found while porting the reader to Rust. There is no sensible behaviour to reproduce, so the Rust replace_missing returns None when the walk leaves the table, and its caller drops the target rather than inventing one.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions