Summary
parse_mf2 (src/endf/mf2.py) selects the unresolved-resonance branch on LRF == 2, but the resolved/unresolved distinction is carried by LRU, not LRF.
https://github.com/shimwell/endf-python/blob/main/src/endf/mf2.py#L42-L53
if LRF == 0:
...
elif LRU in (0, 1):
# resolved resonance region
rrange.update(_FORMALISMS[LRF].dict_from_endf(file_obj, NRO))
elif LRF == 2:
# unresolved resonance region
rrange.update(Unresolved.dict_from_endf(file_obj, LFW, LRF, NRO))
Per ENDF-102, LRU=1 is the resolved region and LRU=2 the unresolved one. Within LRU=2, LRF selects the representation: LRF=1 for the energy-independent cases (Case A, LFW=0; Case B, LFW=1) and LRF=2 for the fully energy-dependent one (Case C).
So a range with LRU=2, LRF=1 matches neither elif: not LRU in (0, 1), and not LRF == 2. It falls through with nothing read.
That Unresolved.dict_from_endf contains explicit LFW == 0 and LRF == 1 and LFW == 1 and LRF == 1 branches — code the current dispatch can never reach — is fairly strong evidence the condition was meant to be LRU == 2.
Reproduction
A minimal MF=2 section with LRU=2, LRF=1, LFW=0 (Case A):
import io
from endf.mf2 import parse_mf2
def f(v): return f"{v: .6E}".replace('E+0','+').replace('E-0','-').replace('E+','+').replace('E-','-').rjust(11)
def i(v): return str(int(v)).rjust(11)
def line(*x): return "".join(x).ljust(66) + "9999 2151\n"
text = (
line(f(95244), f(241.968), i(0), i(0), i(1), i(0)) # HEAD, NIS=1
+ line(f(95244), f(1.0), i(0), i(0), i(1), i(0)) # isotope, LFW=0, NER=1
+ line(f(100.0), f(1000.0), i(2), i(1), i(0), i(1)) # range: LRU=2, LRF=1
+ line(f(2.5), f(0.9), i(0), i(0), i(1), i(0)) # SPI, AP, LSSF, NLS=1
+ line(f(241.968), f(0.0), i(0), i(0), i(6), i(1)) # LIST header, NJS=1
+ line(f(10.0), f(3.0), f(1.0), f(0.5), f(0.04), f(0.0)) # D, AJ, AMUN, GNO, GG
)
r = parse_mf2(io.StringIO(text))['isotopes'][0]['ranges'][0]
print(sorted(r))
['EH', 'EL', 'LRF', 'LRU', 'NAPS', 'NRO']
Only the range header survives. SPI, AP, LSSF, NLS and the whole ranges list are gone.
Effect
- Every unresolved resonance range written with
LRF=1 — Case A and Case B, which is how most actinide and many structural-material evaluations write their URR — returns no parameters at all. There is no error, so a consumer sees an evaluation that simply appears to have no URR data.
- Because the branch also never reads the records, the file position is left before them. Any subsequent range in the same section is then parsed starting from the wrong record, so the corruption is not confined to the range that was skipped.
LRU=2, LRF=2 (Case C) happens to work, since LRF == 2 is true for it. That is why this is not immediately obvious.
Suggested fix
elif LRU == 2:
# unresolved resonance region
rrange.update(Unresolved.dict_from_endf(file_obj, LFW, LRF, NRO))
Worth a test with an evaluation that has a Case A or Case B URR — U238 or Am241 from ENDF/B-VIII would do.
Notes
Found while porting the reader to Rust. The port reproduces the current dispatch deliberately, with a comment pointing here, so the two agree until this is settled; the Case A/B readers are implemented on the Rust side and switch on as soon as the condition is corrected.
Summary
parse_mf2(src/endf/mf2.py) selects the unresolved-resonance branch onLRF == 2, but the resolved/unresolved distinction is carried by LRU, not LRF.https://github.com/shimwell/endf-python/blob/main/src/endf/mf2.py#L42-L53
Per ENDF-102,
LRU=1is the resolved region andLRU=2the unresolved one. WithinLRU=2,LRFselects the representation:LRF=1for the energy-independent cases (Case A,LFW=0; Case B,LFW=1) andLRF=2for the fully energy-dependent one (Case C).So a range with
LRU=2, LRF=1matches neitherelif: notLRU in (0, 1), and notLRF == 2. It falls through with nothing read.That
Unresolved.dict_from_endfcontains explicitLFW == 0 and LRF == 1andLFW == 1 and LRF == 1branches — code the current dispatch can never reach — is fairly strong evidence the condition was meant to beLRU == 2.Reproduction
A minimal MF=2 section with
LRU=2, LRF=1, LFW=0(Case A):Only the range header survives.
SPI,AP,LSSF,NLSand the wholerangeslist are gone.Effect
LRF=1— Case A and Case B, which is how most actinide and many structural-material evaluations write their URR — returns no parameters at all. There is no error, so a consumer sees an evaluation that simply appears to have no URR data.LRU=2, LRF=2(Case C) happens to work, sinceLRF == 2is true for it. That is why this is not immediately obvious.Suggested fix
Worth a test with an evaluation that has a Case A or Case B URR — U238 or Am241 from ENDF/B-VIII would do.
Notes
Found while porting the reader to Rust. The port reproduces the current dispatch deliberately, with a comment pointing here, so the two agree until this is settled; the Case A/B readers are implemented on the Rust side and switch on as soon as the condition is corrected.