riplpy 0.6.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- riplpy/__init__.py +1192 -0
- riplpy/collections.py +474 -0
- riplpy/config.py +397 -0
- riplpy/db.py +1231 -0
- riplpy/densities/__init__.py +94 -0
- riplpy/densities/_comb_tab.py +82 -0
- riplpy/densities/bsfg.py +178 -0
- riplpy/densities/bsk14_comb.py +48 -0
- riplpy/densities/bskg3_comb.py +46 -0
- riplpy/densities/ct.py +140 -0
- riplpy/densities/egsm.py +224 -0
- riplpy/densities/egsm_norm.py +144 -0
- riplpy/densities/examples/__init__.py +0 -0
- riplpy/densities/examples/example1.py +55 -0
- riplpy/densities/examples/example2.py +54 -0
- riplpy/densities/examples/example3.py +61 -0
- riplpy/densities/hfb.py +431 -0
- riplpy/densities/nld.py +21 -0
- riplpy/densities/qrpabe.py +46 -0
- riplpy/densities/shell_corr.py +206 -0
- riplpy/densities/thfb_comb.py +46 -0
- riplpy/elements.py +513 -0
- riplpy/exceptions.py +38 -0
- riplpy/fission/__init__.py +126 -0
- riplpy/fission/bskg3.py +214 -0
- riplpy/fission/core.py +70 -0
- riplpy/fission/d1m.py +149 -0
- riplpy/fission/empire.py +181 -0
- riplpy/fission/empirical.py +219 -0
- riplpy/fission/empirical_new.py +50 -0
- riplpy/fission/examples/__init__.py +0 -0
- riplpy/fission/examples/example1.py +52 -0
- riplpy/fission/hfb.py +242 -0
- riplpy/fission/hfbpath.py +315 -0
- riplpy/fission/nld_fis.py +347 -0
- riplpy/fission/rmf.py +258 -0
- riplpy/gamma/__init__.py +127 -0
- riplpy/gamma/_layout.py +158 -0
- riplpy/gamma/core.py +328 -0
- riplpy/gamma/d1m.py +206 -0
- riplpy/gamma/exp.py +410 -0
- riplpy/gamma/gdr.py +158 -0
- riplpy/gamma/gsf.py +292 -0
- riplpy/gamma/psf.py +242 -0
- riplpy/gamma/smlo_e1.py +234 -0
- riplpy/gamma/smlo_m1.py +97 -0
- riplpy/gamma/systematics.py +138 -0
- riplpy/gamma/tlo.py +201 -0
- riplpy/levels/__init__.py +39 -0
- riplpy/levels/core.py +91 -0
- riplpy/levels/ct.py +172 -0
- riplpy/levels/discrete.py +381 -0
- riplpy/masses/__init__.py +155 -0
- riplpy/masses/ab.py +122 -0
- riplpy/masses/ame20.py +153 -0
- riplpy/masses/bskg3.py +189 -0
- riplpy/masses/core.py +17 -0
- riplpy/masses/d1m.py +172 -0
- riplpy/masses/deformations.py +122 -0
- riplpy/masses/density_bskg3.py +250 -0
- riplpy/masses/density_d1m.py +253 -0
- riplpy/masses/examples/__init__.py +0 -0
- riplpy/masses/examples/example1.py +27 -0
- riplpy/masses/examples/example2.py +30 -0
- riplpy/masses/examples/example3.py +11 -0
- riplpy/masses/examples/example4.py +26 -0
- riplpy/masses/frdm12.py +152 -0
- riplpy/masses/frdm95.py +157 -0
- riplpy/masses/hfb14.py +164 -0
- riplpy/masses/hfb27.py +177 -0
- riplpy/masses/ws4.py +184 -0
- riplpy/models.py +77 -0
- riplpy/optical/__init__.py +192 -0
- riplpy/optical/atomki.py +258 -0
- riplpy/optical/config.py +228 -0
- riplpy/optical/core.py +374 -0
- riplpy/optical/coupled_channel.py +689 -0
- riplpy/optical/deformations.py +319 -0
- riplpy/optical/index.py +404 -0
- riplpy/optical/omp.py +507 -0
- riplpy/optical/reader.py +291 -0
- riplpy/optical/references.py +274 -0
- riplpy/optical/rop2013.py +273 -0
- riplpy/optical/spherical.py +460 -0
- riplpy/resonances/__init__.py +82 -0
- riplpy/resonances/pwave.py +315 -0
- riplpy/resonances/swave.py +40 -0
- riplpy/test.py +54 -0
- riplpy-0.6.2.dist-info/METADATA +522 -0
- riplpy-0.6.2.dist-info/RECORD +93 -0
- riplpy-0.6.2.dist-info/WHEEL +5 -0
- riplpy-0.6.2.dist-info/licenses/LICENSE.txt +43 -0
- riplpy-0.6.2.dist-info/top_level.txt +1 -0
riplpy/__init__.py
ADDED
|
@@ -0,0 +1,1192 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
# Code version
|
|
4
|
+
__version__ = "0.6.2"
|
|
5
|
+
|
|
6
|
+
# Logging
|
|
7
|
+
import logging as _logging
|
|
8
|
+
|
|
9
|
+
# Set up library-level logger with NullHandler (users can configure their own handlers)
|
|
10
|
+
logger = _logging.getLogger(__name__)
|
|
11
|
+
logger.addHandler(_logging.NullHandler())
|
|
12
|
+
|
|
13
|
+
# Local
|
|
14
|
+
from . import config as config
|
|
15
|
+
from .collections import *
|
|
16
|
+
from .models import Models
|
|
17
|
+
from . import densities
|
|
18
|
+
from . import fission
|
|
19
|
+
from . import gamma
|
|
20
|
+
from . import levels
|
|
21
|
+
from . import masses
|
|
22
|
+
from . import optical
|
|
23
|
+
from . import resonances
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Supported RIPL sections
|
|
27
|
+
sections = ('densities', 'fission', 'gamma', 'levels', 'masses', 'optical', 'resonances')
|
|
28
|
+
|
|
29
|
+
# Public API surface (explicit for tooling, autodoc, and ML/AI introspection)
|
|
30
|
+
__all__ = [
|
|
31
|
+
# Core types
|
|
32
|
+
'Nuclide', 'Nucleus', 'Element', 'Elements', 'Models',
|
|
33
|
+
# Sections (submodules)
|
|
34
|
+
'densities', 'fission', 'gamma', 'levels', 'masses', 'optical',
|
|
35
|
+
'resonances', 'config', 'sections',
|
|
36
|
+
# Configuration
|
|
37
|
+
'set_path', 'get_path', 'load',
|
|
38
|
+
# Discovery / introspection
|
|
39
|
+
'in_ripl', 'in_sections', 'in_dbs', 'list_sections', 'list_databases',
|
|
40
|
+
'list_nuclei', 'get_database',
|
|
41
|
+
# Uniform ML/AI ingestion entry points
|
|
42
|
+
'to_dataframe', 'to_records', 'to_numpy',
|
|
43
|
+
# Scalar convenience getters
|
|
44
|
+
'get_mass', 'get_mass_entry', 'get_level_density', 'get_gdr',
|
|
45
|
+
'get_resonance', 'get_fission_barrier',
|
|
46
|
+
# Batch getters
|
|
47
|
+
'get_masses', 'get_mass_entries', 'get_level_densities', 'get_gdrs',
|
|
48
|
+
'get_resonances', 'get_fission_barriers',
|
|
49
|
+
# Optical model
|
|
50
|
+
'get_omp', 'list_omps', 'find_omp', 'get_deformation',
|
|
51
|
+
'get_omp_reference',
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def set_path(path: str) -> None:
|
|
56
|
+
"""Set the RIPL database path.
|
|
57
|
+
|
|
58
|
+
This is the recommended way to configure RIPLpy if the RIPL_LOCATION
|
|
59
|
+
environment variable is not set.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
path: Absolute path to the RIPL database directory
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ValueError: If the path does not exist or is not a directory
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
>>> import riplpy
|
|
69
|
+
>>> riplpy.set_path('/path/to/RIPL-4')
|
|
70
|
+
>>> riplpy.load()
|
|
71
|
+
"""
|
|
72
|
+
config.set_path(path)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_path() -> str:
|
|
76
|
+
"""Get the current RIPL database path.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The resolved RIPL path, or empty string if not configured
|
|
80
|
+
|
|
81
|
+
Examples:
|
|
82
|
+
>>> import riplpy
|
|
83
|
+
>>> print(riplpy.get_path())
|
|
84
|
+
'/usr/local/share/RIPL-4'
|
|
85
|
+
"""
|
|
86
|
+
return config.get_path()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _parallel_load(section: str, directory: str = None) -> None:
|
|
90
|
+
"""Load a single section by name, used by the threaded quick loader.
|
|
91
|
+
|
|
92
|
+
Runs inside a worker thread (see :func:`load` with ``quick=True``). Threads
|
|
93
|
+
share the interpreter's memory, so the section's module-level ``db`` is
|
|
94
|
+
populated in the same process the caller is using — unlike a process pool,
|
|
95
|
+
whose workers would discard their results.
|
|
96
|
+
"""
|
|
97
|
+
if section in sections:
|
|
98
|
+
try:
|
|
99
|
+
module = globals()[section]
|
|
100
|
+
module.load(directory=directory)
|
|
101
|
+
except Exception as exc: # noqa: BLE001
|
|
102
|
+
logger.warning(
|
|
103
|
+
f"load(): skipping section '{section}' (loader error): {exc}"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def load(directory: str = None, quick: bool = False) -> None:
|
|
108
|
+
"""Load the entire RIPL database from a specified directory on the local machine.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
directory: Path to the RIPL database. If not provided, uses the
|
|
112
|
+
configured path (from set_path(), RIPL_LOCATION env var,
|
|
113
|
+
~/.riplpyrc, or auto-detected location).
|
|
114
|
+
quick: If True, load the sections concurrently with a thread pool.
|
|
115
|
+
The readers are I/O-bound, so this overlaps their file parsing
|
|
116
|
+
while still populating the databases in this process.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
ValueError: If no directory is provided and no path is configured.
|
|
120
|
+
|
|
121
|
+
Examples:
|
|
122
|
+
>>> import riplpy
|
|
123
|
+
>>> riplpy.load('/path/to/RIPL') # Explicit path
|
|
124
|
+
>>> # Or configure first, then load
|
|
125
|
+
>>> riplpy.set_path('/path/to/RIPL')
|
|
126
|
+
>>> riplpy.load()
|
|
127
|
+
|
|
128
|
+
Note:
|
|
129
|
+
Once this method is called, the databases cannot be unloaded.
|
|
130
|
+
|
|
131
|
+
A section whose backing data is absent (e.g. a legacy RIPL-3 layout, or
|
|
132
|
+
a partial install) is skipped with a warning rather than aborting the
|
|
133
|
+
whole load, so the databases that *are* available still populate.
|
|
134
|
+
"""
|
|
135
|
+
if directory is None:
|
|
136
|
+
directory = config.get_path()
|
|
137
|
+
if not directory:
|
|
138
|
+
raise ValueError(
|
|
139
|
+
"No RIPL directory specified and no path configured. "
|
|
140
|
+
"Either pass a directory to load(), or configure the path first:\n"
|
|
141
|
+
" - riplpy.set_path('/path/to/RIPL')\n"
|
|
142
|
+
" - Set RIPL_LOCATION environment variable\n"
|
|
143
|
+
" - Create ~/.riplpyrc config file"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if quick:
|
|
147
|
+
# Load sections concurrently with threads. The readers are I/O-bound
|
|
148
|
+
# (parsing data files), so they release the GIL during file reads and
|
|
149
|
+
# genuinely overlap. Threads share memory, so each section's db is
|
|
150
|
+
# populated in this process -- a process pool would load into workers
|
|
151
|
+
# and discard the results. Each section writes to its own module-level
|
|
152
|
+
# db accessor, so there is no cross-thread contention.
|
|
153
|
+
from concurrent.futures import ThreadPoolExecutor as _ThreadPoolExecutor
|
|
154
|
+
with _ThreadPoolExecutor(max_workers=len(sections)) as _executor:
|
|
155
|
+
_futures = [
|
|
156
|
+
_executor.submit(_parallel_load, section, directory)
|
|
157
|
+
for section in sections
|
|
158
|
+
]
|
|
159
|
+
for _future in _futures:
|
|
160
|
+
_future.result() # surface unexpected (non-loader) exceptions
|
|
161
|
+
else:
|
|
162
|
+
for section in sections:
|
|
163
|
+
module = globals()[section]
|
|
164
|
+
try:
|
|
165
|
+
module.load(directory=directory)
|
|
166
|
+
except Exception as exc: # noqa: BLE001
|
|
167
|
+
logger.warning(
|
|
168
|
+
f"load(): skipping section '{section}' (loader error): {exc}"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def in_ripl(x: Nuclide | Element) -> bool:
|
|
173
|
+
"""Check if the given Nuclide (or Element) appears anywhere in the RIPL database. Returns True if so, False otherwise.
|
|
174
|
+
This method may only be used once riplpy.load() has been issued.
|
|
175
|
+
|
|
176
|
+
Input:
|
|
177
|
+
x [Nuclide | Element]: a Nuclide or Element object that may be in the RIPL database
|
|
178
|
+
|
|
179
|
+
Output:
|
|
180
|
+
[bool]: True if x in RIPL, False otherwise
|
|
181
|
+
|
|
182
|
+
Examples:
|
|
183
|
+
(1) Check if input is in the RIPL database::
|
|
184
|
+
$ import riplpy
|
|
185
|
+
$ riplpy.load()
|
|
186
|
+
$ n = riplpy.Nuclide(z=50, a=130)
|
|
187
|
+
$ riplpy.in_ripl(n) # True
|
|
188
|
+
"""
|
|
189
|
+
# Loop over each section of RIPL
|
|
190
|
+
for section in sections:
|
|
191
|
+
# Check the databases in this section
|
|
192
|
+
for db in getattr(globals()[section], 'db')():
|
|
193
|
+
db_obj = getattr(getattr(globals()[section], 'db'), db)
|
|
194
|
+
if db_obj is None:
|
|
195
|
+
# Section loader skipped this database (data file missing).
|
|
196
|
+
continue
|
|
197
|
+
if x in db_obj.data:
|
|
198
|
+
return True
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def in_sections(x: Nuclide | Element) -> list:
|
|
203
|
+
"""Check if the given Nuclide (or Element) is in various sections of the RIPL database. Returns a list of sections where True.
|
|
204
|
+
This method may only be used once riplpy.load() has been issued.
|
|
205
|
+
|
|
206
|
+
Input:
|
|
207
|
+
x [Nuclide | Element]: a Nuclide or Element object that may be in the RIPL database
|
|
208
|
+
|
|
209
|
+
Output:
|
|
210
|
+
[list]: A list of section names (as a string) x is in; empty otherwise
|
|
211
|
+
|
|
212
|
+
Examples:
|
|
213
|
+
(1) Return the section names that the input is in::
|
|
214
|
+
$ import riplpy
|
|
215
|
+
$ riplpy.load() # This will take some time
|
|
216
|
+
$ n = riplpy.Nuclide(Z=46, a=119)
|
|
217
|
+
$ riplpy.in_sections(n) # ['densities', 'gamma', 'levels', 'masses']
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
_sections = []
|
|
221
|
+
# Loop over each section of RIPL
|
|
222
|
+
for section in sections:
|
|
223
|
+
# Check the databases in this section
|
|
224
|
+
for db in getattr(globals()[section], 'db')():
|
|
225
|
+
db_obj = getattr(getattr(globals()[section], 'db'), db)
|
|
226
|
+
if db_obj is None:
|
|
227
|
+
continue
|
|
228
|
+
if x in db_obj.data:
|
|
229
|
+
_sections.append(section)
|
|
230
|
+
break
|
|
231
|
+
return _sections
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def in_dbs(x: Nuclide | Element) -> list:
|
|
235
|
+
"""Check if the given Nuclide (or Element) is in various databases of RIPL. Returns a list of database names where the object can be found.
|
|
236
|
+
This method may only be used once riplpy.load() has been issued.
|
|
237
|
+
|
|
238
|
+
Input:
|
|
239
|
+
x [Nuclide | Element]: a Nuclide or Element object that may be in the RIPL database
|
|
240
|
+
|
|
241
|
+
Output:
|
|
242
|
+
[list]: A list of tuples that includes the section name (as a string) and the database name (as a string) that x is in; empty otherwise
|
|
243
|
+
"""
|
|
244
|
+
dbs = []
|
|
245
|
+
# Loop over each section of RIPL
|
|
246
|
+
for section in sections:
|
|
247
|
+
# Check the databases in this section
|
|
248
|
+
for db in getattr(globals()[section], 'db')():
|
|
249
|
+
db_obj = getattr(getattr(globals()[section], 'db'), db)
|
|
250
|
+
if db_obj is None:
|
|
251
|
+
continue
|
|
252
|
+
if x in db_obj.data:
|
|
253
|
+
dbs.append((section, db))
|
|
254
|
+
return dbs
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def list_sections() -> list:
|
|
258
|
+
"""List all available RIPL sections.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
List of section names (strings)
|
|
262
|
+
|
|
263
|
+
Examples:
|
|
264
|
+
>>> riplpy.list_sections()
|
|
265
|
+
['densities', 'fission', 'gamma', 'levels', 'masses', 'optical', 'resonances']
|
|
266
|
+
"""
|
|
267
|
+
return list(sections)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def list_databases(section: str = None) -> list | dict:
|
|
271
|
+
"""List available databases, optionally filtered by section.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
section: Section name to filter by (e.g., 'masses', 'densities').
|
|
275
|
+
If None, returns a dict of all sections and their databases.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
If section is provided: List of database names in that section
|
|
279
|
+
If section is None: Dict mapping section names to lists of database names
|
|
280
|
+
|
|
281
|
+
Examples:
|
|
282
|
+
>>> riplpy.list_databases('masses')
|
|
283
|
+
['ame20', 'bskg3', 'd1m', 'frdm1995', 'frdm2012', ...]
|
|
284
|
+
>>> riplpy.list_databases()
|
|
285
|
+
{'densities': ['bsfg', 'ct', ...], 'masses': ['ame20', ...], ...}
|
|
286
|
+
|
|
287
|
+
Raises:
|
|
288
|
+
ValueError: If the section name is not recognized
|
|
289
|
+
|
|
290
|
+
Note:
|
|
291
|
+
Databases must be loaded first via riplpy.load() for this to return results.
|
|
292
|
+
"""
|
|
293
|
+
if section is not None:
|
|
294
|
+
if section not in sections:
|
|
295
|
+
raise ValueError(f"Unknown section '{section}'. Available: {list(sections)}")
|
|
296
|
+
module = globals()[section]
|
|
297
|
+
return list(getattr(module, 'db')())
|
|
298
|
+
|
|
299
|
+
# Return all sections
|
|
300
|
+
result = {}
|
|
301
|
+
for sec in sections:
|
|
302
|
+
module = globals()[sec]
|
|
303
|
+
result[sec] = list(getattr(module, 'db')())
|
|
304
|
+
return result
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def list_nuclei(database: str, section: str = None) -> list:
|
|
308
|
+
"""List all nuclei available in a specific database.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
database: Database name (e.g., 'ame20', 'bsfg'). Can also use
|
|
312
|
+
'section.database' format (e.g., 'masses.ame20').
|
|
313
|
+
section: Section name. Required if database name is ambiguous
|
|
314
|
+
across sections, optional if using 'section.database' format.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
List of Nuclide objects available in the database
|
|
318
|
+
|
|
319
|
+
Examples:
|
|
320
|
+
>>> riplpy.list_nuclei('ame20') # Unambiguous
|
|
321
|
+
[Nuclide(Z=1, A=1), Nuclide(Z=1, A=2), ...]
|
|
322
|
+
>>> riplpy.list_nuclei('masses.ame20') # Explicit section
|
|
323
|
+
[Nuclide(Z=1, A=1), Nuclide(Z=1, A=2), ...]
|
|
324
|
+
>>> riplpy.list_nuclei('ct', section='densities') # Disambiguate
|
|
325
|
+
[Nuclide(Z=11, A=24), ...]
|
|
326
|
+
|
|
327
|
+
Raises:
|
|
328
|
+
ValueError: If the database is not found or is ambiguous
|
|
329
|
+
|
|
330
|
+
Note:
|
|
331
|
+
Databases must be loaded first via riplpy.load() for this to return results.
|
|
332
|
+
"""
|
|
333
|
+
# Handle 'section.database' format
|
|
334
|
+
if '.' in database:
|
|
335
|
+
parts = database.split('.', 1)
|
|
336
|
+
section = parts[0]
|
|
337
|
+
database = parts[1]
|
|
338
|
+
|
|
339
|
+
# If section is specified, use it directly
|
|
340
|
+
if section is not None:
|
|
341
|
+
if section not in sections:
|
|
342
|
+
raise ValueError(f"Unknown section '{section}'. Available: {list(sections)}")
|
|
343
|
+
module = globals()[section]
|
|
344
|
+
db_accessor = getattr(module, 'db')
|
|
345
|
+
if not db_accessor.has(database):
|
|
346
|
+
available = list(db_accessor())
|
|
347
|
+
raise ValueError(f"Database '{database}' not found in section '{section}'. Available: {available}")
|
|
348
|
+
db = getattr(db_accessor, db_accessor.resolve(database))
|
|
349
|
+
return list(db.data.keys())
|
|
350
|
+
|
|
351
|
+
# Search all sections for the database
|
|
352
|
+
found_in = []
|
|
353
|
+
for sec in sections:
|
|
354
|
+
module = globals()[sec]
|
|
355
|
+
db_accessor = getattr(module, 'db')
|
|
356
|
+
if db_accessor.has(database):
|
|
357
|
+
found_in.append(sec)
|
|
358
|
+
|
|
359
|
+
if len(found_in) == 0:
|
|
360
|
+
raise ValueError(f"Database '{database}' not found in any section")
|
|
361
|
+
elif len(found_in) > 1:
|
|
362
|
+
raise ValueError(f"Database '{database}' is ambiguous, found in sections: {found_in}. "
|
|
363
|
+
f"Please specify section, e.g., list_nuclei('{found_in[0]}.{database}')")
|
|
364
|
+
|
|
365
|
+
# Unambiguous - return the nuclei
|
|
366
|
+
module = globals()[found_in[0]]
|
|
367
|
+
db_accessor = getattr(module, 'db')
|
|
368
|
+
db = getattr(db_accessor, db_accessor.resolve(database))
|
|
369
|
+
return list(db.data.keys())
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def get_database(database: str, section: str = None) -> object:
|
|
373
|
+
"""Get a database object by name.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
database: Database name (e.g., 'ame20', 'bsfg'). Can also use
|
|
377
|
+
'section.database' format (e.g., 'masses.ame20').
|
|
378
|
+
section: Section name. Required if database name is ambiguous
|
|
379
|
+
across sections, optional if using 'section.database' format.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
The database object
|
|
383
|
+
|
|
384
|
+
Examples:
|
|
385
|
+
>>> db = riplpy.get_database('ame20')
|
|
386
|
+
>>> db = riplpy.get_database('masses.ame20')
|
|
387
|
+
>>> db = riplpy.get_database('ct', section='levels')
|
|
388
|
+
|
|
389
|
+
Raises:
|
|
390
|
+
ValueError: If the database is not found or is ambiguous
|
|
391
|
+
|
|
392
|
+
Note:
|
|
393
|
+
Databases must be loaded first via riplpy.load() for this to return results.
|
|
394
|
+
"""
|
|
395
|
+
# Handle 'section.database' format
|
|
396
|
+
if '.' in database:
|
|
397
|
+
parts = database.split('.', 1)
|
|
398
|
+
section = parts[0]
|
|
399
|
+
database = parts[1]
|
|
400
|
+
|
|
401
|
+
# If section is specified, use it directly
|
|
402
|
+
if section is not None:
|
|
403
|
+
if section not in sections:
|
|
404
|
+
raise ValueError(f"Unknown section '{section}'. Available: {list(sections)}")
|
|
405
|
+
module = globals()[section]
|
|
406
|
+
db_accessor = getattr(module, 'db')
|
|
407
|
+
if not db_accessor.has(database):
|
|
408
|
+
available = list(db_accessor())
|
|
409
|
+
raise ValueError(f"Database '{database}' not found in section '{section}'. Available: {available}")
|
|
410
|
+
return getattr(db_accessor, db_accessor.resolve(database))
|
|
411
|
+
|
|
412
|
+
# Search all sections for the database
|
|
413
|
+
found_in = []
|
|
414
|
+
for sec in sections:
|
|
415
|
+
module = globals()[sec]
|
|
416
|
+
db_accessor = getattr(module, 'db')
|
|
417
|
+
if db_accessor.has(database):
|
|
418
|
+
found_in.append(sec)
|
|
419
|
+
|
|
420
|
+
if len(found_in) == 0:
|
|
421
|
+
raise ValueError(f"Database '{database}' not found in any section")
|
|
422
|
+
elif len(found_in) > 1:
|
|
423
|
+
raise ValueError(f"Database '{database}' is ambiguous, found in sections: {found_in}. "
|
|
424
|
+
f"Please specify section, e.g., get_database('{found_in[0]}.{database}')")
|
|
425
|
+
|
|
426
|
+
# Unambiguous - return the database
|
|
427
|
+
module = globals()[found_in[0]]
|
|
428
|
+
db_accessor = getattr(module, 'db')
|
|
429
|
+
return getattr(db_accessor, db_accessor.resolve(database))
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def to_dataframe(database: str, section: str = None) -> "pandas.DataFrame":
|
|
433
|
+
"""Return a loaded database as a pandas DataFrame.
|
|
434
|
+
|
|
435
|
+
A single uniform ML-ingestion entry point: every section's databases —
|
|
436
|
+
scalar or array/spectral — flatten to a tidy DataFrame with the nuclide
|
|
437
|
+
Z/A/symbol columns first. Array fields are kept as native Python lists so
|
|
438
|
+
downstream code can consume them directly.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
database: Database name or 'section.database' (e.g. 'masses.ame20',
|
|
442
|
+
'gamma.experiment_slo', 'fission.bskg3').
|
|
443
|
+
section: Optional section name if the database name is ambiguous.
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
pandas.DataFrame of the flattened entries.
|
|
447
|
+
|
|
448
|
+
Raises:
|
|
449
|
+
ValueError: If the database is unknown/ambiguous, or is not loaded
|
|
450
|
+
(call riplpy.load() or the section/per-element loader first).
|
|
451
|
+
|
|
452
|
+
Examples:
|
|
453
|
+
>>> riplpy.load()
|
|
454
|
+
>>> df = riplpy.to_dataframe('masses.ame20')
|
|
455
|
+
>>> df = riplpy.to_dataframe('fission.bskg3')
|
|
456
|
+
|
|
457
|
+
Note:
|
|
458
|
+
Databases must be loaded first (riplpy.load(), the section load(), or
|
|
459
|
+
a per-element/per-nucleus helper for the heavy array datasets).
|
|
460
|
+
"""
|
|
461
|
+
db = get_database(database, section)
|
|
462
|
+
if db is None:
|
|
463
|
+
raise ValueError(
|
|
464
|
+
f"Database '{database}' is not loaded (it may be a RIPL-3 legacy "
|
|
465
|
+
f"product absent from this release, or a heavy dataset not yet "
|
|
466
|
+
f"loaded). Load it first via riplpy.load() or the per-element "
|
|
467
|
+
f"loader."
|
|
468
|
+
)
|
|
469
|
+
return db.to_dataframe()
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def to_records(database: str, section: str = None) -> list:
|
|
473
|
+
"""Return a loaded database as a list of serialized entry dictionaries.
|
|
474
|
+
|
|
475
|
+
Like :func:`to_dataframe` but returns plain JSON-serializable records
|
|
476
|
+
(Nuclide objects become ``{'Z','A','symbol'}``; arrays stay lists) — a
|
|
477
|
+
uniform, dependency-free ML/AI ingestion format.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
database: Database name or 'section.database'.
|
|
481
|
+
section: Optional section name if ambiguous.
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
list[dict]: One serialized record per entry (per dataset for the
|
|
485
|
+
list-valued PSF database).
|
|
486
|
+
|
|
487
|
+
Raises:
|
|
488
|
+
ValueError: If the database is unknown/ambiguous or not loaded.
|
|
489
|
+
"""
|
|
490
|
+
db = get_database(database, section)
|
|
491
|
+
if db is None:
|
|
492
|
+
raise ValueError(
|
|
493
|
+
f"Database '{database}' is not loaded (RIPL-3 legacy or a heavy "
|
|
494
|
+
f"dataset not yet loaded). Load it first."
|
|
495
|
+
)
|
|
496
|
+
return db.to_list()
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def to_numpy(database: str, section: str = None, structured: bool = True):
|
|
500
|
+
"""Return a loaded database as a numpy array for ML/AI pipelines.
|
|
501
|
+
|
|
502
|
+
A uniform numpy ingestion entry point parallel to :func:`to_dataframe`.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
database: Database name or 'section.database' (e.g. 'masses.ame20',
|
|
506
|
+
'gamma.experiment_slo', 'fission.bskg3_barriers').
|
|
507
|
+
section: Optional section name if the database name is ambiguous.
|
|
508
|
+
structured: If True (default), return a numpy structured (record)
|
|
509
|
+
array with every column preserved (Z/A/symbol first). If False,
|
|
510
|
+
return ``(X, columns)`` where ``X`` is a dense float64 feature
|
|
511
|
+
matrix of the numeric columns only.
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
numpy.ndarray when ``structured`` is True; otherwise a
|
|
515
|
+
``(numpy.ndarray, list[str])`` tuple.
|
|
516
|
+
|
|
517
|
+
Raises:
|
|
518
|
+
ValueError: If the database is unknown/ambiguous or not loaded.
|
|
519
|
+
ImportError: If numpy is not installed (pip install 'riplpy[numpy]').
|
|
520
|
+
|
|
521
|
+
Examples:
|
|
522
|
+
>>> arr = riplpy.to_numpy('masses.ame20')
|
|
523
|
+
>>> X, cols = riplpy.to_numpy('masses.ame20', structured=False)
|
|
524
|
+
"""
|
|
525
|
+
db = get_database(database, section)
|
|
526
|
+
if db is None:
|
|
527
|
+
raise ValueError(
|
|
528
|
+
f"Database '{database}' is not loaded (RIPL-3 legacy or a heavy "
|
|
529
|
+
f"dataset not yet loaded). Load it first."
|
|
530
|
+
)
|
|
531
|
+
return db.to_numpy(structured=structured)
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def _resolve_nucleus(Z_or_nuclide, A=None) -> Nuclide:
|
|
535
|
+
"""Resolve input to a Nuclide object.
|
|
536
|
+
|
|
537
|
+
Accepts either:
|
|
538
|
+
- A Nuclide/Nucleus object directly
|
|
539
|
+
- Z and A as separate arguments
|
|
540
|
+
"""
|
|
541
|
+
if isinstance(Z_or_nuclide, (Nuclide, Nucleus)):
|
|
542
|
+
return Z_or_nuclide
|
|
543
|
+
if A is None:
|
|
544
|
+
raise ValueError("Must provide either a Nuclide or both Z and A")
|
|
545
|
+
return Nuclide(Z=Z_or_nuclide, A=A)
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def get_mass(Z_or_nuclide, A: int = None, model: str = 'ame20') -> float:
|
|
549
|
+
"""Get the mass excess for a nucleus from a specified mass model.
|
|
550
|
+
|
|
551
|
+
Args:
|
|
552
|
+
Z_or_nuclide: Either a Nuclide/Nucleus object, or the atomic number Z
|
|
553
|
+
A: Mass number (required if Z_or_nuclide is an integer)
|
|
554
|
+
model: Mass model to use. Options: 'ame20', 'frdm12', 'frdm95',
|
|
555
|
+
'hfb14', 'hfb27', 'bskg3', 'd1m'. Default: 'ame20'
|
|
556
|
+
|
|
557
|
+
Returns:
|
|
558
|
+
Mass excess in MeV
|
|
559
|
+
|
|
560
|
+
Examples:
|
|
561
|
+
>>> riplpy.get_mass(82, 208) # Using Z, A
|
|
562
|
+
-21.749
|
|
563
|
+
>>> riplpy.get_mass(Nuclide(Z=82, A=208), model='frdm12')
|
|
564
|
+
-21.352
|
|
565
|
+
|
|
566
|
+
Raises:
|
|
567
|
+
ValueError: If the model is not recognized
|
|
568
|
+
NucleusNotFoundError: If the nucleus is not in the database
|
|
569
|
+
"""
|
|
570
|
+
n = _resolve_nucleus(Z_or_nuclide, A)
|
|
571
|
+
|
|
572
|
+
if model not in masses.MASS_MODELS:
|
|
573
|
+
raise ValueError(f"Unknown mass model '{model}'. Available: {list(masses.MASS_MODELS.keys())}")
|
|
574
|
+
|
|
575
|
+
db_name, field = masses.MASS_MODELS[model]
|
|
576
|
+
db = getattr(masses.db, db_name)
|
|
577
|
+
entry = db.get(n)
|
|
578
|
+
return getattr(entry, field)
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def get_mass_entry(Z_or_nuclide, A: int = None, model: str = 'ame20') -> object:
|
|
582
|
+
"""Get the full mass entry for a nucleus from a specified mass model.
|
|
583
|
+
|
|
584
|
+
Args:
|
|
585
|
+
Z_or_nuclide: Either a Nuclide/Nucleus object, or the atomic number Z
|
|
586
|
+
A: Mass number (required if Z_or_nuclide is an integer)
|
|
587
|
+
model: Mass model to use. Options: 'ame20', 'frdm12', 'frdm95',
|
|
588
|
+
'hfb14', 'hfb27', 'bskg3', 'd1m'. Default: 'ame20'
|
|
589
|
+
|
|
590
|
+
Returns:
|
|
591
|
+
Entry object with all available mass data for that model
|
|
592
|
+
|
|
593
|
+
Raises:
|
|
594
|
+
ValueError: If the model is not recognized
|
|
595
|
+
NucleusNotFoundError: If the nucleus is not in the database
|
|
596
|
+
"""
|
|
597
|
+
n = _resolve_nucleus(Z_or_nuclide, A)
|
|
598
|
+
|
|
599
|
+
if model not in masses.MASS_MODELS:
|
|
600
|
+
raise ValueError(f"Unknown mass model '{model}'. Available: {list(masses.MASS_MODELS.keys())}")
|
|
601
|
+
|
|
602
|
+
db_name, _ = masses.MASS_MODELS[model]
|
|
603
|
+
db = getattr(masses.db, db_name)
|
|
604
|
+
return db.get(n)
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def get_level_density(Z_or_nuclide, A: int = None, model: str = 'egsm') -> object:
|
|
608
|
+
"""Get level density parameters for a nucleus from a specified model.
|
|
609
|
+
|
|
610
|
+
Args:
|
|
611
|
+
Z_or_nuclide: Either a Nuclide/Nucleus object, or the atomic number Z
|
|
612
|
+
A: Mass number (required if Z_or_nuclide is an integer)
|
|
613
|
+
model: Level density model. RIPL-4 options: 'egsm', 'egsm_norm',
|
|
614
|
+
'bsk14_comb', 'bskg3_comb', 'qrpabe', 'thfb_comb'. RIPL-3
|
|
615
|
+
legacy options (full RIPL distribution only): 'bsfg', 'ct',
|
|
616
|
+
'hfb'. Default: 'egsm'
|
|
617
|
+
|
|
618
|
+
Returns:
|
|
619
|
+
Entry object containing level density parameters (model-dependent fields)
|
|
620
|
+
|
|
621
|
+
Raises:
|
|
622
|
+
ValueError: If the model is not recognized
|
|
623
|
+
NucleusNotFoundError: If the nucleus is not in the database
|
|
624
|
+
"""
|
|
625
|
+
n = _resolve_nucleus(Z_or_nuclide, A)
|
|
626
|
+
|
|
627
|
+
if model not in densities.LEVEL_DENSITY_MODELS:
|
|
628
|
+
raise ValueError(f"Unknown level density model '{model}'. Available: {list(densities.LEVEL_DENSITY_MODELS.keys())}")
|
|
629
|
+
|
|
630
|
+
from riplpy.exceptions import NucleusNotFoundError
|
|
631
|
+
db = getattr(densities.db, densities.LEVEL_DENSITY_MODELS[model])
|
|
632
|
+
if db is None:
|
|
633
|
+
raise NucleusNotFoundError(
|
|
634
|
+
f"Level density model '{model}' database not loaded (data file missing)"
|
|
635
|
+
)
|
|
636
|
+
return db.get(n)
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def get_gdr(Z_or_nuclide, A: int = None) -> object:
|
|
640
|
+
"""Get Giant Dipole Resonance (GDR) parameters for a nucleus.
|
|
641
|
+
|
|
642
|
+
Args:
|
|
643
|
+
Z_or_nuclide: Either a Nuclide/Nucleus object, or the atomic number Z
|
|
644
|
+
A: Mass number (required if Z_or_nuclide is an integer)
|
|
645
|
+
|
|
646
|
+
Returns:
|
|
647
|
+
Entry object containing GDR parameters
|
|
648
|
+
|
|
649
|
+
Raises:
|
|
650
|
+
NucleusNotFoundError: If the nucleus is not in the database
|
|
651
|
+
"""
|
|
652
|
+
from riplpy.exceptions import NucleusNotFoundError
|
|
653
|
+
n = _resolve_nucleus(Z_or_nuclide, A)
|
|
654
|
+
db = gamma.db.theory_gdr
|
|
655
|
+
if db is None:
|
|
656
|
+
raise NucleusNotFoundError(
|
|
657
|
+
"Theoretical GDR database not loaded (gdr-parameters-theor.dat missing)"
|
|
658
|
+
)
|
|
659
|
+
return db.get(n)
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def get_resonance(Z_or_nuclide, A: int = None, wave: str = 's') -> object:
|
|
663
|
+
"""Get resonance parameters for a nucleus.
|
|
664
|
+
|
|
665
|
+
Args:
|
|
666
|
+
Z_or_nuclide: Either a Nuclide/Nucleus object, or the atomic number Z
|
|
667
|
+
A: Mass number (required if Z_or_nuclide is an integer)
|
|
668
|
+
wave: Resonance type - 's' (s-wave) or 'p' (p-wave). Default: 's'
|
|
669
|
+
|
|
670
|
+
Returns:
|
|
671
|
+
Entry object containing resonance parameters
|
|
672
|
+
|
|
673
|
+
Raises:
|
|
674
|
+
ValueError: If the wave type is not recognized
|
|
675
|
+
RiplFileNotFoundError: If the resonance database was skipped at load
|
|
676
|
+
time (e.g. legacy RIPL-3 layout or a partial install)
|
|
677
|
+
NucleusNotFoundError: If the nucleus is not in the database
|
|
678
|
+
"""
|
|
679
|
+
from riplpy.exceptions import RiplFileNotFoundError
|
|
680
|
+
|
|
681
|
+
n = _resolve_nucleus(Z_or_nuclide, A)
|
|
682
|
+
|
|
683
|
+
if wave == 's':
|
|
684
|
+
wave_db = resonances.db.swave
|
|
685
|
+
elif wave == 'p':
|
|
686
|
+
wave_db = resonances.db.pwave
|
|
687
|
+
else:
|
|
688
|
+
raise ValueError(f"Unknown wave type '{wave}'. Available: 's', 'p'")
|
|
689
|
+
|
|
690
|
+
if wave_db is None:
|
|
691
|
+
raise RiplFileNotFoundError(
|
|
692
|
+
f"The {wave}-wave resonance database is not loaded. The RIPL-4 "
|
|
693
|
+
f"file (resonances_L{0 if wave == 's' else 1}.dat) was not found "
|
|
694
|
+
f"at the configured path; the legacy RIPL-3 "
|
|
695
|
+
f"resonances{0 if wave == 's' else 1}.dat format is not supported. "
|
|
696
|
+
f"Point RIPL_LOCATION at a RIPL-4 (github-layout) tree."
|
|
697
|
+
)
|
|
698
|
+
return wave_db.get(n)
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def get_fission_barrier(Z_or_nuclide, A: int = None, model: str = 'empirical') -> object:
|
|
702
|
+
"""Get fission barrier parameters for a nucleus.
|
|
703
|
+
|
|
704
|
+
Args:
|
|
705
|
+
Z_or_nuclide: Either a Nuclide/Nucleus object, or the atomic number Z
|
|
706
|
+
A: Mass number (required if Z_or_nuclide is an integer)
|
|
707
|
+
model: Barrier model. Options: 'empirical', 'empirical_new', 'empire',
|
|
708
|
+
'hfb', 'bskg3'. Default: 'empirical'
|
|
709
|
+
|
|
710
|
+
Returns:
|
|
711
|
+
Entry object containing fission barrier parameters
|
|
712
|
+
|
|
713
|
+
Raises:
|
|
714
|
+
ValueError: If the model is not recognized
|
|
715
|
+
NucleusNotFoundError: If the nucleus is not in the database
|
|
716
|
+
"""
|
|
717
|
+
n = _resolve_nucleus(Z_or_nuclide, A)
|
|
718
|
+
|
|
719
|
+
if model not in fission.BARRIER_MODELS:
|
|
720
|
+
raise ValueError(f"Unknown fission barrier model '{model}'. Available: {list(fission.BARRIER_MODELS.keys())}")
|
|
721
|
+
|
|
722
|
+
from riplpy.exceptions import NucleusNotFoundError
|
|
723
|
+
db = getattr(fission.db, fission.BARRIER_MODELS[model])
|
|
724
|
+
if db is None:
|
|
725
|
+
raise NucleusNotFoundError(
|
|
726
|
+
f"Fission barrier model '{model}' database not loaded (data file missing)"
|
|
727
|
+
)
|
|
728
|
+
return db.get(n)
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def _normalize_nuclei_list(nuclei: list) -> list:
|
|
732
|
+
"""Convert a list of nuclei specifications to Nuclide objects.
|
|
733
|
+
|
|
734
|
+
Accepts:
|
|
735
|
+
- Nuclide/Nucleus objects
|
|
736
|
+
- Tuples of (Z, A)
|
|
737
|
+
- Lists of [Z, A]
|
|
738
|
+
|
|
739
|
+
Args:
|
|
740
|
+
nuclei: List of nuclei in any supported format
|
|
741
|
+
|
|
742
|
+
Returns:
|
|
743
|
+
List of Nuclide objects
|
|
744
|
+
"""
|
|
745
|
+
result = []
|
|
746
|
+
for item in nuclei:
|
|
747
|
+
if isinstance(item, (Nuclide, Nucleus)):
|
|
748
|
+
result.append(item)
|
|
749
|
+
elif isinstance(item, (tuple, list)) and len(item) >= 2:
|
|
750
|
+
result.append(Nuclide(Z=item[0], A=item[1]))
|
|
751
|
+
else:
|
|
752
|
+
raise ValueError(f"Cannot convert {item} to Nuclide. Expected Nuclide, (Z, A) tuple, or [Z, A] list.")
|
|
753
|
+
return result
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def get_masses(nuclei: list, model: str = 'ame20', skip_missing: bool = False) -> dict:
|
|
757
|
+
"""Get mass excess values for multiple nuclei.
|
|
758
|
+
|
|
759
|
+
Args:
|
|
760
|
+
nuclei: List of nuclei. Each element can be:
|
|
761
|
+
- A Nuclide/Nucleus object
|
|
762
|
+
- A tuple (Z, A)
|
|
763
|
+
- A list [Z, A]
|
|
764
|
+
model: Mass model to use. Options: 'ame20', 'frdm12', 'frdm95',
|
|
765
|
+
'hfb14', 'hfb27', 'bskg3', 'd1m'. Default: 'ame20'
|
|
766
|
+
skip_missing: If True, skip nuclei not in the database.
|
|
767
|
+
If False (default), raise NucleusNotFoundError.
|
|
768
|
+
|
|
769
|
+
Returns:
|
|
770
|
+
Dictionary mapping Nuclide -> mass excess (MeV)
|
|
771
|
+
|
|
772
|
+
Examples:
|
|
773
|
+
>>> nuclei = [Nuclide(82, 208), Nuclide(82, 206), (82, 204)]
|
|
774
|
+
>>> masses = riplpy.get_masses(nuclei, model='ame20')
|
|
775
|
+
>>> masses[Nuclide(82, 208)]
|
|
776
|
+
-21.749
|
|
777
|
+
|
|
778
|
+
>>> # Using tuples for convenience
|
|
779
|
+
>>> masses = riplpy.get_masses([(26, 56), (28, 58), (50, 120)])
|
|
780
|
+
|
|
781
|
+
Raises:
|
|
782
|
+
ValueError: If the model is not recognized
|
|
783
|
+
NucleusNotFoundError: If a nucleus is not found and skip_missing=False
|
|
784
|
+
"""
|
|
785
|
+
from riplpy.exceptions import NucleusNotFoundError
|
|
786
|
+
|
|
787
|
+
if model not in masses.MASS_MODELS:
|
|
788
|
+
raise ValueError(f"Unknown mass model '{model}'. Available: {list(masses.MASS_MODELS.keys())}")
|
|
789
|
+
|
|
790
|
+
db_name, field = masses.MASS_MODELS[model]
|
|
791
|
+
db = getattr(masses.db, db_name)
|
|
792
|
+
|
|
793
|
+
nuclide_list = _normalize_nuclei_list(nuclei)
|
|
794
|
+
result = {}
|
|
795
|
+
|
|
796
|
+
for n in nuclide_list:
|
|
797
|
+
try:
|
|
798
|
+
entry = db.get(n)
|
|
799
|
+
result[n] = getattr(entry, field)
|
|
800
|
+
except KeyError:
|
|
801
|
+
# NucleusNotFoundError subclasses KeyError; a few Database
|
|
802
|
+
# subclasses still raise the plain KeyError.
|
|
803
|
+
if not skip_missing:
|
|
804
|
+
raise
|
|
805
|
+
# skip_missing=True: just don't include in results
|
|
806
|
+
|
|
807
|
+
return result
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
def get_mass_entries(nuclei: list, model: str = 'ame20', skip_missing: bool = False) -> dict:
|
|
811
|
+
"""Get full mass entries for multiple nuclei.
|
|
812
|
+
|
|
813
|
+
Args:
|
|
814
|
+
nuclei: List of nuclei. Each element can be:
|
|
815
|
+
- A Nuclide/Nucleus object
|
|
816
|
+
- A tuple (Z, A)
|
|
817
|
+
- A list [Z, A]
|
|
818
|
+
model: Mass model to use. Options: 'ame20', 'frdm12', 'frdm95',
|
|
819
|
+
'hfb14', 'hfb27', 'bskg3', 'd1m'. Default: 'ame20'
|
|
820
|
+
skip_missing: If True, skip nuclei not in the database.
|
|
821
|
+
If False (default), raise NucleusNotFoundError.
|
|
822
|
+
|
|
823
|
+
Returns:
|
|
824
|
+
Dictionary mapping Nuclide -> Entry object
|
|
825
|
+
|
|
826
|
+
Examples:
|
|
827
|
+
>>> nuclei = [(82, 208), (82, 206), (82, 204)]
|
|
828
|
+
>>> entries = riplpy.get_mass_entries(nuclei)
|
|
829
|
+
>>> entries[Nuclide(82, 208)].Mexp
|
|
830
|
+
-21.749
|
|
831
|
+
|
|
832
|
+
Raises:
|
|
833
|
+
ValueError: If the model is not recognized
|
|
834
|
+
NucleusNotFoundError: If a nucleus is not found and skip_missing=False
|
|
835
|
+
"""
|
|
836
|
+
from riplpy.exceptions import NucleusNotFoundError
|
|
837
|
+
|
|
838
|
+
if model not in masses.MASS_MODELS:
|
|
839
|
+
raise ValueError(f"Unknown mass model '{model}'. Available: {list(masses.MASS_MODELS.keys())}")
|
|
840
|
+
|
|
841
|
+
db_name, _ = masses.MASS_MODELS[model]
|
|
842
|
+
db = getattr(masses.db, db_name)
|
|
843
|
+
|
|
844
|
+
nuclide_list = _normalize_nuclei_list(nuclei)
|
|
845
|
+
result = {}
|
|
846
|
+
|
|
847
|
+
for n in nuclide_list:
|
|
848
|
+
try:
|
|
849
|
+
result[n] = db.get(n)
|
|
850
|
+
except KeyError:
|
|
851
|
+
# NucleusNotFoundError subclasses KeyError; a few Database
|
|
852
|
+
# subclasses still raise the plain KeyError.
|
|
853
|
+
if not skip_missing:
|
|
854
|
+
raise
|
|
855
|
+
|
|
856
|
+
return result
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
def get_level_densities(nuclei: list, model: str = 'egsm', skip_missing: bool = False) -> dict:
|
|
860
|
+
"""Get level density parameters for multiple nuclei.
|
|
861
|
+
|
|
862
|
+
Args:
|
|
863
|
+
nuclei: List of nuclei. Each element can be:
|
|
864
|
+
- A Nuclide/Nucleus object
|
|
865
|
+
- A tuple (Z, A)
|
|
866
|
+
- A list [Z, A]
|
|
867
|
+
model: Level density model. RIPL-4 options: 'egsm', 'egsm_norm',
|
|
868
|
+
'bsk14_comb', 'bskg3_comb', 'qrpabe', 'thfb_comb'. RIPL-3
|
|
869
|
+
legacy options (full RIPL distribution only): 'bsfg', 'ct',
|
|
870
|
+
'hfb'. Default: 'egsm'
|
|
871
|
+
skip_missing: If True, skip nuclei not in the database.
|
|
872
|
+
If False (default), raise NucleusNotFoundError.
|
|
873
|
+
|
|
874
|
+
Returns:
|
|
875
|
+
Dictionary mapping Nuclide -> Entry object
|
|
876
|
+
|
|
877
|
+
Examples:
|
|
878
|
+
>>> nuclei = [(11, 24), (12, 26), (13, 28)]
|
|
879
|
+
>>> densities = riplpy.get_level_densities(nuclei, model='egsm')
|
|
880
|
+
|
|
881
|
+
Raises:
|
|
882
|
+
ValueError: If the model is not recognized
|
|
883
|
+
NucleusNotFoundError: If a nucleus is not found and skip_missing=False
|
|
884
|
+
"""
|
|
885
|
+
from riplpy.exceptions import NucleusNotFoundError
|
|
886
|
+
|
|
887
|
+
if model not in densities.LEVEL_DENSITY_MODELS:
|
|
888
|
+
raise ValueError(f"Unknown level density model '{model}'. Available: {list(densities.LEVEL_DENSITY_MODELS.keys())}")
|
|
889
|
+
|
|
890
|
+
db = getattr(densities.db, densities.LEVEL_DENSITY_MODELS[model])
|
|
891
|
+
|
|
892
|
+
nuclide_list = _normalize_nuclei_list(nuclei)
|
|
893
|
+
result = {}
|
|
894
|
+
|
|
895
|
+
# The underlying database may be ``None`` if the backing data file is
|
|
896
|
+
# absent from this RIPL layout. Honour ``skip_missing`` in that case.
|
|
897
|
+
if db is None:
|
|
898
|
+
if skip_missing:
|
|
899
|
+
return result
|
|
900
|
+
raise NucleusNotFoundError(
|
|
901
|
+
f"Level density model '{model}' database not loaded (data file missing)"
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
for n in nuclide_list:
|
|
905
|
+
try:
|
|
906
|
+
result[n] = db.get(n)
|
|
907
|
+
except KeyError:
|
|
908
|
+
# NucleusNotFoundError subclasses KeyError; a few Database
|
|
909
|
+
# subclasses still raise the plain KeyError.
|
|
910
|
+
if not skip_missing:
|
|
911
|
+
raise
|
|
912
|
+
|
|
913
|
+
return result
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
def get_gdrs(nuclei: list, skip_missing: bool = False) -> dict:
|
|
917
|
+
"""Get Giant Dipole Resonance (GDR) parameters for multiple nuclei.
|
|
918
|
+
|
|
919
|
+
Args:
|
|
920
|
+
nuclei: List of nuclei. Each element can be:
|
|
921
|
+
- A Nuclide/Nucleus object
|
|
922
|
+
- A tuple (Z, A)
|
|
923
|
+
- A list [Z, A]
|
|
924
|
+
skip_missing: If True, skip nuclei not in the database.
|
|
925
|
+
If False (default), raise NucleusNotFoundError.
|
|
926
|
+
|
|
927
|
+
Returns:
|
|
928
|
+
Dictionary mapping Nuclide -> Entry object
|
|
929
|
+
|
|
930
|
+
Examples:
|
|
931
|
+
>>> nuclei = [(82, 208), (50, 120), (28, 58)]
|
|
932
|
+
>>> gdrs = riplpy.get_gdrs(nuclei)
|
|
933
|
+
|
|
934
|
+
Raises:
|
|
935
|
+
NucleusNotFoundError: If a nucleus is not found and skip_missing=False
|
|
936
|
+
"""
|
|
937
|
+
from riplpy.exceptions import NucleusNotFoundError
|
|
938
|
+
|
|
939
|
+
nuclide_list = _normalize_nuclei_list(nuclei)
|
|
940
|
+
result = {}
|
|
941
|
+
|
|
942
|
+
db = gamma.db.theory_gdr
|
|
943
|
+
if db is None:
|
|
944
|
+
if skip_missing:
|
|
945
|
+
return result
|
|
946
|
+
raise NucleusNotFoundError(
|
|
947
|
+
"Theoretical GDR database not loaded (gdr-parameters-theor.dat missing)"
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
for n in nuclide_list:
|
|
951
|
+
try:
|
|
952
|
+
result[n] = db.get(n)
|
|
953
|
+
except KeyError:
|
|
954
|
+
# NucleusNotFoundError subclasses KeyError; a few Database
|
|
955
|
+
# subclasses still raise the plain KeyError.
|
|
956
|
+
if not skip_missing:
|
|
957
|
+
raise
|
|
958
|
+
|
|
959
|
+
return result
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
def get_resonances(nuclei: list, wave: str = 's', skip_missing: bool = False) -> dict:
|
|
963
|
+
"""Get resonance parameters for multiple nuclei.
|
|
964
|
+
|
|
965
|
+
Args:
|
|
966
|
+
nuclei: List of nuclei. Each element can be:
|
|
967
|
+
- A Nuclide/Nucleus object
|
|
968
|
+
- A tuple (Z, A)
|
|
969
|
+
- A list [Z, A]
|
|
970
|
+
wave: Resonance type - 's' (s-wave) or 'p' (p-wave). Default: 's'
|
|
971
|
+
skip_missing: If True, skip nuclei not in the database.
|
|
972
|
+
If False (default), raise NucleusNotFoundError.
|
|
973
|
+
|
|
974
|
+
Returns:
|
|
975
|
+
Dictionary mapping Nuclide -> Entry object
|
|
976
|
+
|
|
977
|
+
Examples:
|
|
978
|
+
>>> nuclei = [(82, 208), (50, 120)]
|
|
979
|
+
>>> resonances = riplpy.get_resonances(nuclei, wave='s')
|
|
980
|
+
|
|
981
|
+
Raises:
|
|
982
|
+
ValueError: If the wave type is not recognized
|
|
983
|
+
NucleusNotFoundError: If a nucleus is not found and skip_missing=False
|
|
984
|
+
"""
|
|
985
|
+
from riplpy.exceptions import NucleusNotFoundError
|
|
986
|
+
|
|
987
|
+
if wave == 's':
|
|
988
|
+
db = resonances.db.swave
|
|
989
|
+
elif wave == 'p':
|
|
990
|
+
db = resonances.db.pwave
|
|
991
|
+
else:
|
|
992
|
+
raise ValueError(f"Unknown wave type '{wave}'. Available: 's', 'p'")
|
|
993
|
+
|
|
994
|
+
nuclide_list = _normalize_nuclei_list(nuclei)
|
|
995
|
+
result = {}
|
|
996
|
+
|
|
997
|
+
for n in nuclide_list:
|
|
998
|
+
try:
|
|
999
|
+
result[n] = db.get(n)
|
|
1000
|
+
except KeyError:
|
|
1001
|
+
# NucleusNotFoundError subclasses KeyError; a few Database
|
|
1002
|
+
# subclasses still raise the plain KeyError.
|
|
1003
|
+
if not skip_missing:
|
|
1004
|
+
raise
|
|
1005
|
+
|
|
1006
|
+
return result
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
def get_fission_barriers(nuclei: list, model: str = 'empirical', skip_missing: bool = False) -> dict:
|
|
1010
|
+
"""Get fission barrier parameters for multiple nuclei.
|
|
1011
|
+
|
|
1012
|
+
Args:
|
|
1013
|
+
nuclei: List of nuclei. Each element can be:
|
|
1014
|
+
- A Nuclide/Nucleus object
|
|
1015
|
+
- A tuple (Z, A)
|
|
1016
|
+
- A list [Z, A]
|
|
1017
|
+
model: Barrier model. Options: 'empirical', 'empirical_new', 'empire',
|
|
1018
|
+
'hfb', 'bskg3'. Default: 'empirical'
|
|
1019
|
+
skip_missing: If True, skip nuclei not in the database.
|
|
1020
|
+
If False (default), raise NucleusNotFoundError.
|
|
1021
|
+
|
|
1022
|
+
Returns:
|
|
1023
|
+
Dictionary mapping Nuclide -> Entry object
|
|
1024
|
+
|
|
1025
|
+
Examples:
|
|
1026
|
+
>>> nuclei = [(92, 235), (92, 238), (94, 239)]
|
|
1027
|
+
>>> barriers = riplpy.get_fission_barriers(nuclei, model='hfb')
|
|
1028
|
+
|
|
1029
|
+
Raises:
|
|
1030
|
+
ValueError: If the model is not recognized
|
|
1031
|
+
NucleusNotFoundError: If a nucleus is not found and skip_missing=False
|
|
1032
|
+
"""
|
|
1033
|
+
from riplpy.exceptions import NucleusNotFoundError
|
|
1034
|
+
|
|
1035
|
+
if model not in fission.BARRIER_MODELS:
|
|
1036
|
+
raise ValueError(f"Unknown fission barrier model '{model}'. Available: {list(fission.BARRIER_MODELS.keys())}")
|
|
1037
|
+
|
|
1038
|
+
db = getattr(fission.db, fission.BARRIER_MODELS[model])
|
|
1039
|
+
|
|
1040
|
+
nuclide_list = _normalize_nuclei_list(nuclei)
|
|
1041
|
+
result = {}
|
|
1042
|
+
|
|
1043
|
+
if db is None:
|
|
1044
|
+
if skip_missing:
|
|
1045
|
+
return result
|
|
1046
|
+
raise NucleusNotFoundError(
|
|
1047
|
+
f"Fission barrier model '{model}' database not loaded (data file missing)"
|
|
1048
|
+
)
|
|
1049
|
+
|
|
1050
|
+
for n in nuclide_list:
|
|
1051
|
+
try:
|
|
1052
|
+
result[n] = db.get(n)
|
|
1053
|
+
except KeyError:
|
|
1054
|
+
# NucleusNotFoundError subclasses KeyError; a few Database
|
|
1055
|
+
# subclasses still raise the plain KeyError.
|
|
1056
|
+
if not skip_missing:
|
|
1057
|
+
raise
|
|
1058
|
+
|
|
1059
|
+
return result
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
def get_omp(iref: int) -> object:
|
|
1063
|
+
"""Get an optical model potential by its reference number.
|
|
1064
|
+
|
|
1065
|
+
Args:
|
|
1066
|
+
iref: Reference number of the potential (1-2500+)
|
|
1067
|
+
|
|
1068
|
+
Returns:
|
|
1069
|
+
SphericalOMP or CoupledChannelOMP object
|
|
1070
|
+
|
|
1071
|
+
Examples:
|
|
1072
|
+
>>> pot = riplpy.get_omp(2405)
|
|
1073
|
+
>>> print(pot.projectile)
|
|
1074
|
+
'n'
|
|
1075
|
+
>>> print(pot.header.author)
|
|
1076
|
+
'Koning'
|
|
1077
|
+
|
|
1078
|
+
Raises:
|
|
1079
|
+
KeyError: If the potential is not found
|
|
1080
|
+
|
|
1081
|
+
Note:
|
|
1082
|
+
The optical database must be loaded first via riplpy.load()
|
|
1083
|
+
"""
|
|
1084
|
+
return optical.db.potentials.get(iref)
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
def list_omps(projectile: str = None) -> list:
|
|
1088
|
+
"""List all optical model potential reference numbers.
|
|
1089
|
+
|
|
1090
|
+
Args:
|
|
1091
|
+
projectile: Optional projectile filter ('n', 'p', 'd', 't', 'h', 'a').
|
|
1092
|
+
If None, returns all potentials.
|
|
1093
|
+
|
|
1094
|
+
Returns:
|
|
1095
|
+
List of iref values (integers)
|
|
1096
|
+
|
|
1097
|
+
Examples:
|
|
1098
|
+
>>> all_irefs = riplpy.list_omps()
|
|
1099
|
+
>>> neutron_irefs = riplpy.list_omps(projectile='n')
|
|
1100
|
+
|
|
1101
|
+
Note:
|
|
1102
|
+
The optical database must be loaded first via riplpy.load()
|
|
1103
|
+
"""
|
|
1104
|
+
db = optical.db.potentials
|
|
1105
|
+
if projectile is not None:
|
|
1106
|
+
db = db.filter_by_projectile(projectile)
|
|
1107
|
+
return list(db.data.keys())
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
def find_omp(projectile: str, Z: int, A: int, E: float = None) -> list:
|
|
1111
|
+
"""Find optical model potentials suitable for a specific reaction.
|
|
1112
|
+
|
|
1113
|
+
Args:
|
|
1114
|
+
projectile: Projectile type ('n', 'p', 'd', 't', 'h', 'a')
|
|
1115
|
+
Z: Target atomic number
|
|
1116
|
+
A: Target mass number
|
|
1117
|
+
E: Optional projectile energy in MeV. If provided, filters to
|
|
1118
|
+
potentials valid at this energy.
|
|
1119
|
+
|
|
1120
|
+
Returns:
|
|
1121
|
+
List of matching OMP objects, sorted by relevance
|
|
1122
|
+
|
|
1123
|
+
Examples:
|
|
1124
|
+
>>> # Find neutron potentials for Pb-208
|
|
1125
|
+
>>> pots = riplpy.find_omp('n', 82, 208)
|
|
1126
|
+
>>> print(len(pots))
|
|
1127
|
+
15
|
|
1128
|
+
|
|
1129
|
+
>>> # Find potentials valid at 14 MeV
|
|
1130
|
+
>>> pots = riplpy.find_omp('n', 82, 208, E=14.0)
|
|
1131
|
+
|
|
1132
|
+
Note:
|
|
1133
|
+
The optical database must be loaded first via riplpy.load()
|
|
1134
|
+
"""
|
|
1135
|
+
return optical.db.potentials.find_for_reaction(projectile, Z, A, E)
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
def get_deformation(Z: int, A: int, Ex: float = 0.0, L: int = 2) -> object:
|
|
1139
|
+
"""Get nuclear deformation parameter.
|
|
1140
|
+
|
|
1141
|
+
Args:
|
|
1142
|
+
Z: Atomic number
|
|
1143
|
+
A: Mass number
|
|
1144
|
+
Ex: Excitation energy in MeV (default 0.0 for low-lying states)
|
|
1145
|
+
L: Deformation order (2 for quadrupole, 3 for octupole). Default: 2
|
|
1146
|
+
|
|
1147
|
+
Returns:
|
|
1148
|
+
DeformationEntry object with beta value and metadata
|
|
1149
|
+
|
|
1150
|
+
Examples:
|
|
1151
|
+
>>> # Get quadrupole deformation for Pu-239
|
|
1152
|
+
>>> deform = riplpy.get_deformation(94, 239)
|
|
1153
|
+
>>> print(deform.beta)
|
|
1154
|
+
0.3699
|
|
1155
|
+
|
|
1156
|
+
>>> # Get excited state deformation for U-238
|
|
1157
|
+
>>> deform = riplpy.get_deformation(92, 238, Ex=0.045)
|
|
1158
|
+
|
|
1159
|
+
Raises:
|
|
1160
|
+
KeyError: If the deformation is not found
|
|
1161
|
+
|
|
1162
|
+
Note:
|
|
1163
|
+
The optical database must be loaded first via riplpy.load().
|
|
1164
|
+
The database contains excited-level deformations derived from
|
|
1165
|
+
experiments. Use get_for_nucleus() for all available entries.
|
|
1166
|
+
"""
|
|
1167
|
+
return optical.db.deformations.get(Z, A, Ex, L)
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
def get_omp_reference(ref_num: int) -> object:
|
|
1171
|
+
"""Get bibliographic reference for an optical model potential.
|
|
1172
|
+
|
|
1173
|
+
Args:
|
|
1174
|
+
ref_num: Reference number
|
|
1175
|
+
|
|
1176
|
+
Returns:
|
|
1177
|
+
Reference object with citation text
|
|
1178
|
+
|
|
1179
|
+
Examples:
|
|
1180
|
+
>>> ref = riplpy.get_omp_reference(100)
|
|
1181
|
+
>>> print(ref.citation)
|
|
1182
|
+
'A.J.Koning and J.P.Delaroche, Nucl.Phys. A713 (2003) 231'
|
|
1183
|
+
>>> print(ref.year)
|
|
1184
|
+
2003
|
|
1185
|
+
|
|
1186
|
+
Raises:
|
|
1187
|
+
KeyError: If the reference is not found
|
|
1188
|
+
|
|
1189
|
+
Note:
|
|
1190
|
+
The optical database must be loaded first via riplpy.load()
|
|
1191
|
+
"""
|
|
1192
|
+
return optical.db.references.get(ref_num)
|