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.
Files changed (93) hide show
  1. riplpy/__init__.py +1192 -0
  2. riplpy/collections.py +474 -0
  3. riplpy/config.py +397 -0
  4. riplpy/db.py +1231 -0
  5. riplpy/densities/__init__.py +94 -0
  6. riplpy/densities/_comb_tab.py +82 -0
  7. riplpy/densities/bsfg.py +178 -0
  8. riplpy/densities/bsk14_comb.py +48 -0
  9. riplpy/densities/bskg3_comb.py +46 -0
  10. riplpy/densities/ct.py +140 -0
  11. riplpy/densities/egsm.py +224 -0
  12. riplpy/densities/egsm_norm.py +144 -0
  13. riplpy/densities/examples/__init__.py +0 -0
  14. riplpy/densities/examples/example1.py +55 -0
  15. riplpy/densities/examples/example2.py +54 -0
  16. riplpy/densities/examples/example3.py +61 -0
  17. riplpy/densities/hfb.py +431 -0
  18. riplpy/densities/nld.py +21 -0
  19. riplpy/densities/qrpabe.py +46 -0
  20. riplpy/densities/shell_corr.py +206 -0
  21. riplpy/densities/thfb_comb.py +46 -0
  22. riplpy/elements.py +513 -0
  23. riplpy/exceptions.py +38 -0
  24. riplpy/fission/__init__.py +126 -0
  25. riplpy/fission/bskg3.py +214 -0
  26. riplpy/fission/core.py +70 -0
  27. riplpy/fission/d1m.py +149 -0
  28. riplpy/fission/empire.py +181 -0
  29. riplpy/fission/empirical.py +219 -0
  30. riplpy/fission/empirical_new.py +50 -0
  31. riplpy/fission/examples/__init__.py +0 -0
  32. riplpy/fission/examples/example1.py +52 -0
  33. riplpy/fission/hfb.py +242 -0
  34. riplpy/fission/hfbpath.py +315 -0
  35. riplpy/fission/nld_fis.py +347 -0
  36. riplpy/fission/rmf.py +258 -0
  37. riplpy/gamma/__init__.py +127 -0
  38. riplpy/gamma/_layout.py +158 -0
  39. riplpy/gamma/core.py +328 -0
  40. riplpy/gamma/d1m.py +206 -0
  41. riplpy/gamma/exp.py +410 -0
  42. riplpy/gamma/gdr.py +158 -0
  43. riplpy/gamma/gsf.py +292 -0
  44. riplpy/gamma/psf.py +242 -0
  45. riplpy/gamma/smlo_e1.py +234 -0
  46. riplpy/gamma/smlo_m1.py +97 -0
  47. riplpy/gamma/systematics.py +138 -0
  48. riplpy/gamma/tlo.py +201 -0
  49. riplpy/levels/__init__.py +39 -0
  50. riplpy/levels/core.py +91 -0
  51. riplpy/levels/ct.py +172 -0
  52. riplpy/levels/discrete.py +381 -0
  53. riplpy/masses/__init__.py +155 -0
  54. riplpy/masses/ab.py +122 -0
  55. riplpy/masses/ame20.py +153 -0
  56. riplpy/masses/bskg3.py +189 -0
  57. riplpy/masses/core.py +17 -0
  58. riplpy/masses/d1m.py +172 -0
  59. riplpy/masses/deformations.py +122 -0
  60. riplpy/masses/density_bskg3.py +250 -0
  61. riplpy/masses/density_d1m.py +253 -0
  62. riplpy/masses/examples/__init__.py +0 -0
  63. riplpy/masses/examples/example1.py +27 -0
  64. riplpy/masses/examples/example2.py +30 -0
  65. riplpy/masses/examples/example3.py +11 -0
  66. riplpy/masses/examples/example4.py +26 -0
  67. riplpy/masses/frdm12.py +152 -0
  68. riplpy/masses/frdm95.py +157 -0
  69. riplpy/masses/hfb14.py +164 -0
  70. riplpy/masses/hfb27.py +177 -0
  71. riplpy/masses/ws4.py +184 -0
  72. riplpy/models.py +77 -0
  73. riplpy/optical/__init__.py +192 -0
  74. riplpy/optical/atomki.py +258 -0
  75. riplpy/optical/config.py +228 -0
  76. riplpy/optical/core.py +374 -0
  77. riplpy/optical/coupled_channel.py +689 -0
  78. riplpy/optical/deformations.py +319 -0
  79. riplpy/optical/index.py +404 -0
  80. riplpy/optical/omp.py +507 -0
  81. riplpy/optical/reader.py +291 -0
  82. riplpy/optical/references.py +274 -0
  83. riplpy/optical/rop2013.py +273 -0
  84. riplpy/optical/spherical.py +460 -0
  85. riplpy/resonances/__init__.py +82 -0
  86. riplpy/resonances/pwave.py +315 -0
  87. riplpy/resonances/swave.py +40 -0
  88. riplpy/test.py +54 -0
  89. riplpy-0.6.2.dist-info/METADATA +522 -0
  90. riplpy-0.6.2.dist-info/RECORD +93 -0
  91. riplpy-0.6.2.dist-info/WHEEL +5 -0
  92. riplpy-0.6.2.dist-info/licenses/LICENSE.txt +43 -0
  93. 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)