irma 1.0.0__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 (76) hide show
  1. irma/__init__.py +66 -0
  2. irma/__main__.py +7 -0
  3. irma/cli.py +198 -0
  4. irma/core/__init__.py +1 -0
  5. irma/core/constants.py +88 -0
  6. irma/core/crystal.py +915 -0
  7. irma/core/crystal_cards.py +745 -0
  8. irma/core/crystal_input.py +137 -0
  9. irma/core/deck.py +583 -0
  10. irma/core/driver.py +761 -0
  11. irma/core/elastic_dw.py +251 -0
  12. irma/core/elastic_extinction.py +369 -0
  13. irma/core/endf_writer.py +1536 -0
  14. irma/core/engine.py +102 -0
  15. irma/core/extinction.py +456 -0
  16. irma/core/grids.py +410 -0
  17. irma/core/incoherent_dw.py +252 -0
  18. irma/core/kernels.py +1624 -0
  19. irma/core/noncubic_engine.py +2149 -0
  20. irma/core/noncubic_helpers.py +265 -0
  21. irma/core/noncubic_inelastic.py +70 -0
  22. irma/core/noncubic_inelastic_context.py +648 -0
  23. irma/core/noncubic_numerics.py +329 -0
  24. irma/core/noncubic_workers.py +1542 -0
  25. irma/core/nuclear_data.py +760 -0
  26. irma/core/phonopy_io.py +1082 -0
  27. irma/core/sab_grids.py +98 -0
  28. irma/core/standalone_sab.py +458 -0
  29. irma/gui/__init__.py +1 -0
  30. irma/gui/app.py +235 -0
  31. irma/gui/deck_text.py +540 -0
  32. irma/gui/element_table.py +540 -0
  33. irma/gui/endf_form.py +3404 -0
  34. irma/gui/grid_form.py +357 -0
  35. irma/gui/mlip_panel.py +1166 -0
  36. irma/gui/ncrystal_panel.py +627 -0
  37. irma/gui/ns_panel.py +1952 -0
  38. irma/gui/runner.py +318 -0
  39. irma/gui/widgets.py +415 -0
  40. irma/mlip/__init__.py +6 -0
  41. irma/mlip/bundle.py +609 -0
  42. irma/mlip/calculators.py +856 -0
  43. irma/mlip/cli.py +626 -0
  44. irma/mlip/emit.py +935 -0
  45. irma/mlip/envs.py +614 -0
  46. irma/mlip/force_server.py +173 -0
  47. irma/mlip/phonons.py +348 -0
  48. irma/mlip/relax.py +257 -0
  49. irma/ncrystal/__init__.py +55 -0
  50. irma/ncrystal/__main__.py +90 -0
  51. irma/ncrystal/build.py +670 -0
  52. irma/ncrystal/config.py +449 -0
  53. irma/ncrystal/convert.py +161 -0
  54. irma/ncrystal/ncmat.py +132 -0
  55. irma/ncrystal/pack.py +404 -0
  56. irma/ncrystal/provenance.py +123 -0
  57. irma/spectra/__init__.py +67 -0
  58. irma/spectra/__main__.py +7 -0
  59. irma/spectra/chopper_resolution.py +646 -0
  60. irma/spectra/cli.py +751 -0
  61. irma/spectra/config.py +1105 -0
  62. irma/spectra/dos_from_phonopy.py +83 -0
  63. irma/spectra/dos_io.py +117 -0
  64. irma/spectra/dos_mode0.py +412 -0
  65. irma/spectra/elastic.py +937 -0
  66. irma/spectra/forward.py +1091 -0
  67. irma/spectra/instruments.py +281 -0
  68. irma/spectra/sqe.py +544 -0
  69. irma-1.0.0.dist-info/METADATA +584 -0
  70. irma-1.0.0.dist-info/RECORD +76 -0
  71. irma-1.0.0.dist-info/WHEEL +5 -0
  72. irma-1.0.0.dist-info/entry_points.txt +5 -0
  73. irma-1.0.0.dist-info/licenses/LICENSE +30 -0
  74. irma-1.0.0.dist-info/licenses/LICENSES/Apache-2.0.txt +202 -0
  75. irma-1.0.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +101 -0
  76. irma-1.0.0.dist-info/top_level.txt +1 -0
irma/__init__.py ADDED
@@ -0,0 +1,66 @@
1
+ """IRMA — (In)elastic Representation of Materials As S(alpha,beta) evaluations.
2
+
3
+ A standalone tool for generating thermal neutron scattering law S(alpha, beta)
4
+ libraries in ENDF-6 format using the phonon expansion method.
5
+
6
+ Supports:
7
+ - Continuous phonon frequency distributions
8
+ - Generalized coherent elastic (Bragg edges) for arbitrary crystals
9
+ - Phonopy-backed hybrid inelastic_mode paths for noncubic MT4 generation
10
+ - Free-gas and diffusion translational modes
11
+ - Discrete oscillators
12
+ - Cold hydrogen/deuterium (ortho/para)
13
+ - Automatic alpha/beta grid generation
14
+ - Neutron-scattering forward model (irma.spectra): powder S(Q,E) ->
15
+ instrument-resolved 1-D spectrum for VISION / indirect / direct geometries,
16
+ with an auto chopper-resolution model for the 8 PyChop direct instruments
17
+ """
18
+
19
+ __version__ = "1.0.0"
20
+ __author__ = "IRMA developers"
21
+
22
+ # Pin native BLAS/OMP thread pools to 1 at package import, before numpy can be
23
+ # loaded through irma. The mode-1/2 engine parallelizes across SPAWNED worker
24
+ # processes (get_context("spawn")); a multithreaded BLAS in the parent or the
25
+ # workers oversubscribes the cores under the process pool (measured >10x
26
+ # slower). Doing it here covers the common `import irma` / CLI entry;
27
+ # setdefault preserves a deliberate caller override (a host that imports numpy
28
+ # before irma should set these itself, or accept threaded BLAS).
29
+ import os as _os
30
+
31
+ for _v in ("OMP_NUM_THREADS", "OPENBLAS_NUM_THREADS", "MKL_NUM_THREADS",
32
+ "NUMEXPR_NUM_THREADS", "VECLIB_MAXIMUM_THREADS", "BLIS_NUM_THREADS"):
33
+ _os.environ.setdefault(_v, "1")
34
+
35
+ # Stable top-level public API. Importing these from `irma` directly
36
+ # (`from irma import run_leapr, DeckError`) decouples callers from the internal
37
+ # module layout, so the core modules can be refactored without breaking them.
38
+ # Exposed LAZILY (PEP 562 __getattr__) so `import irma` stays light -- the heavy
39
+ # engine is only imported when one of these names is actually accessed, and the
40
+ # light core (numpy + endf-parserpy) install never pulls an optional extra.
41
+ __all__ = ["run_leapr", "LeaprResult", "parse_leapr_input", "DeckError",
42
+ "__version__", "__author__"]
43
+
44
+ # public name -> "module:attr" it is loaded from on first access
45
+ _PUBLIC_API = {
46
+ "run_leapr": "irma.core.engine:run_leapr",
47
+ "LeaprResult": "irma.core.driver:LeaprResult",
48
+ "parse_leapr_input": "irma.core.deck:parse_leapr_input",
49
+ "DeckError": "irma.core.deck:DeckError",
50
+ }
51
+
52
+
53
+ def __getattr__(name):
54
+ """Lazily resolve the public API symbols (PEP 562)."""
55
+ target = _PUBLIC_API.get(name)
56
+ if target is None:
57
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
58
+ import importlib
59
+ module_name, attr = target.split(":")
60
+ value = getattr(importlib.import_module(module_name), attr)
61
+ globals()[name] = value # cache so later access skips __getattr__
62
+ return value
63
+
64
+
65
+ def __dir__():
66
+ return sorted(set(globals()) | set(__all__))
irma/__main__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Allow running IRMA as: python -m irma"""
2
+ import sys
3
+
4
+ from irma.cli import main
5
+
6
+ if __name__ == "__main__":
7
+ sys.exit(main())
irma/cli.py ADDED
@@ -0,0 +1,198 @@
1
+ """Command-line interface for IRMA.
2
+
3
+ Four capabilities behind one ``irma`` entry point:
4
+
5
+ irma <deck> <out.endf> ENDF/TSL evaluation (legacy, unchanged)
6
+ irma evaluate <deck> <out.endf> same, explicit (alias: run)
7
+ irma spectra vision|indirect|direct ... neutron-scattering forward spectra
8
+ irma spectra run <config> -o out.csv
9
+ irma ncrystal -o <outdir> config.yaml NCrystal .irmapack export
10
+ irma --gui | --version
11
+
12
+ Backward compatibility is mandatory: if the first argument is not a known
13
+ subcommand keyword, it is treated as the legacy 2-positional deck path, so
14
+ ``irma deck out.endf``, ``python -m irma deck out.endf`` and
15
+ ``python -m irma.core.engine deck out.endf`` keep working byte-for-byte.
16
+
17
+ ``main(argv=None)`` returns a process exit code; the console-script and
18
+ ``python -m irma`` wrappers both ``sys.exit(main())``.
19
+ """
20
+
21
+ import os
22
+ import sys
23
+
24
+
25
+ def _print_help():
26
+ """Print the top-level CLI usage text."""
27
+ print("IRMA — (In)elastic Representation of Materials "
28
+ "As S(alpha,beta) evaluations")
29
+ print()
30
+ print("Usage:")
31
+ print(" irma <input_file> <output_file> "
32
+ "Run ENDF/TSL evaluation from an input deck")
33
+ print(" irma evaluate <input_file> <output_file> "
34
+ "Same, explicit (alias: run)")
35
+ print(" irma spectra vision|indirect|direct ... "
36
+ "Neutron-scattering forward spectrum")
37
+ print(" irma spectra run <config> -o out.csv "
38
+ "Run a spectra config file")
39
+ print(" irma ncrystal -o <outdir> config.yaml "
40
+ "Bake NCrystal .irmapack set (alias: python -m irma.ncrystal)")
41
+ print(" irma mlip build <structure> -o <outdir> "
42
+ "MLIP phonon model bundle (no DFT needed)")
43
+ print(" irma mlip emit <bundle> --to endf,... "
44
+ "Generate IRMA inputs from a bundle")
45
+ print(" irma mlip validate <bundle> "
46
+ "Check a bundle end to end")
47
+ print(" irma --gui "
48
+ "Launch graphical interface")
49
+ print(" irma --version "
50
+ "Show version")
51
+
52
+
53
+ def _launch_gui():
54
+ """Start the Tk GUI, with a clear message if tkinter is unavailable."""
55
+ try:
56
+ from irma.gui.app import main as gui_main
57
+ except ImportError as exc:
58
+ print(f"\nThe GUI could not be started: {exc}", file=sys.stderr)
59
+ print("The graphical interface requires tkinter, which is not "
60
+ "installed for this Python.", file=sys.stderr)
61
+ print("See the Tkinter section of INSTALL.md for how to install "
62
+ "it on your system.", file=sys.stderr)
63
+ return 4
64
+ gui_main()
65
+ return 0
66
+
67
+
68
+ def validate_output_path(path):
69
+ """Pre-flight an output file path BEFORE a long compute (shared by the deck
70
+ and spectra CLIs). Catches the common fail-late cases: the target is an
71
+ existing directory, the parent directory is missing, or the parent is not
72
+ writable. Returns an error message on failure, else None (each caller picks
73
+ its own exit code / stream)."""
74
+ p = os.path.abspath(str(path))
75
+ if os.path.isdir(p):
76
+ return f"output path is a directory, not a file: {p}"
77
+ if os.path.exists(p) and not os.access(p, os.W_OK):
78
+ # An existing read-only target otherwise fails only at the
79
+ # post-compute open (review S10).
80
+ return f"output file exists and is not writable: {p}"
81
+ parent = os.path.dirname(p)
82
+ if not os.path.isdir(parent):
83
+ return f"output directory does not exist: {parent}"
84
+ if not os.access(parent, os.W_OK):
85
+ return f"output directory is not writable: {parent}"
86
+ return None
87
+
88
+
89
+ def _run_deck(args):
90
+ """Legacy 2-positional ENDF/TSL evaluation (run_leapr), unchanged."""
91
+ # `irma evaluate --help` / `irma run --help` is the natural discovery
92
+ # command; asking for help must never be answered with an error.
93
+ if args and args[0] in ("-h", "--help"):
94
+ _print_help()
95
+ return 0
96
+ if len(args) < 2:
97
+ print("Error: both input_file and output_file are required.", file=sys.stderr)
98
+ print("Usage: irma <input_file> <output_file>", file=sys.stderr)
99
+ return 1
100
+ input_file, output_file = args[0], args[1]
101
+ # Mirror the output-side preflight: a directory passed as the input (a
102
+ # one-keystroke tab-completion slip) must fail with a clear message, not
103
+ # leak an IsADirectoryError traceback out of the deck parser's open().
104
+ if os.path.isdir(input_file):
105
+ print(f"Error: input path is a directory, not a file: {input_file}",
106
+ file=sys.stderr)
107
+ print("Pass the LEAPR-style input deck itself "
108
+ "(e.g. graphite.input).", file=sys.stderr)
109
+ return 1
110
+ if not os.path.isfile(input_file):
111
+ print(f"Error: input file not found: {input_file}", file=sys.stderr)
112
+ return 1
113
+ # Pre-validate the output path BEFORE the (multi-minute) compute, so a typo'd
114
+ # output path / directory target / read-only parent fails fast instead of only
115
+ # after the whole run finishes.
116
+ err = validate_output_path(output_file)
117
+ if err:
118
+ print(f"Error: {err}", file=sys.stderr)
119
+ return 1
120
+ from irma.core.engine import run_leapr, DeckError
121
+ try:
122
+ run_leapr(input_file, output_file)
123
+ except DeckError as exc:
124
+ print(f"\nInput deck error:\n {exc}", file=sys.stderr)
125
+ print("Fix the input deck and rerun (see the README card-by-card "
126
+ "input reference).", file=sys.stderr)
127
+ return 2
128
+ except (RuntimeError, OSError, ValueError) as exc:
129
+ # User-meaningful failures (phonopy model loading, missing/unreadable
130
+ # files, semantic problems) get a clean message; a traceback is
131
+ # reserved for genuinely unexpected errors. OSError covers the
132
+ # filesystem backstops (FileNotFoundError, IsADirectoryError,
133
+ # PermissionError) for anything the preflights above did not catch.
134
+ print(f"\nIRMA failed: {exc}", file=sys.stderr)
135
+ return 3
136
+ return 0
137
+
138
+
139
+ def main(argv=None):
140
+ """Console entry point: dispatch to help, GUI, spectra, ncrystal, or a
141
+ deck evaluation depending on the arguments."""
142
+ argv = list(sys.argv[1:] if argv is None else argv)
143
+
144
+ if not argv:
145
+ _print_help()
146
+ return 0
147
+ if argv[0] == "--version":
148
+ from irma import __version__
149
+ print(f"IRMA v{__version__}")
150
+ return 0
151
+ if argv[0] in ("--help", "-h"):
152
+ _print_help()
153
+ return 0
154
+ if argv[0] == "--gui":
155
+ return _launch_gui()
156
+ if argv[0] == "spectra":
157
+ from irma.spectra.cli import main as spectra_main
158
+ return spectra_main(argv[1:])
159
+ if argv[0] == "mlip" and (
160
+ len(argv) == 1
161
+ or argv[1] in ("build", "emit", "validate", "env", "-h",
162
+ "--help")
163
+ or not os.path.exists("mlip")):
164
+ from irma.mlip.cli import main as mlip_main
165
+ return mlip_main(argv[1:])
166
+ if argv[0] == "ncrystal":
167
+ # Same code path as `python -m irma.ncrystal` (which keeps working);
168
+ # surfaced here so all three capabilities live behind one command.
169
+ from irma.ncrystal.__main__ import main as ncrystal_main
170
+ return ncrystal_main(argv[1:])
171
+ if argv[0] in ("evaluate", "run"):
172
+ return _run_deck(argv[1:])
173
+
174
+ # Fall-through: argv[0] is not a known subcommand. Before treating it as a
175
+ # legacy deck path, catch an obvious mistyped subcommand or unknown flag so
176
+ # the user gets a clear hint instead of a misleading "input file not found".
177
+ tok = argv[0]
178
+ if not os.path.exists(tok):
179
+ if tok.startswith("-"):
180
+ print(f"Error: unknown option {tok!r}", file=sys.stderr)
181
+ _print_help()
182
+ return 1
183
+ # only second-guess COMMAND-LIKE tokens (a bare word); a path-like token
184
+ # with a "." or "/" is a (missing) deck path -> let _run_deck report that.
185
+ if "." not in tok and "/" not in tok and os.sep not in tok:
186
+ import difflib
187
+ known = ["evaluate", "run", "spectra", "ncrystal", "mlip",
188
+ "--gui", "--version", "--help"]
189
+ close = difflib.get_close_matches(tok, known, n=1, cutoff=0.7)
190
+ if close:
191
+ print(f"Error: unknown command {tok!r}. Did you mean {close[0]!r}?",
192
+ file=sys.stderr)
193
+ return 1
194
+ return _run_deck(argv)
195
+
196
+
197
+ if __name__ == "__main__":
198
+ sys.exit(main())
irma/core/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Core computation engine for IRMA."""
irma/core/constants.py ADDED
@@ -0,0 +1,88 @@
1
+ """Physical constants and element data shared across IRMA core modules.
2
+
3
+ The numeric values match NJOY2016's physics module exactly (bit-compatible
4
+ LEAPR reproduction depends on it).
5
+ """
6
+
7
+ # ============================================================================
8
+ # Physical constants (matching NJOY2016 physics module exactly)
9
+ # ============================================================================
10
+ BK = 8.617333262e-5 # Boltzmann constant, eV/K
11
+ EV = 1.602176634e-12 # erg/eV
12
+ CLIGHT = 2.99792458e10 # cm/s
13
+ AMU = 931.49410242e6 * EV / (CLIGHT * CLIGHT) # g/amu
14
+ HBAR = 6.582119569e-16 * EV # Planck/2pi, erg*s
15
+ AMASSN = 1.00866491595 # neutron mass in amu
16
+
17
+ THERM = 0.0253 # thermal energy in eV (for lat=1 scaling)
18
+
19
+ # WL2EKIN = h²/(2 m_n) [eV·Å²] (de Broglie E = h²/(2 m_n λ²); CODATA 2018, matches NCrystal wl2ekin from NCDefs.hh)
20
+ WL2EKIN: float = 0.081804209605330899
21
+
22
+ # ============================================================================
23
+ # CODATA 2018 / SI 2019 constants (phonopy-backed noncubic paths)
24
+ # ============================================================================
25
+ # References:
26
+ # E. Tiesinga, P. J. Mohr, D. B. Newell, and B. N. Taylor, "CODATA
27
+ # recommended values of the fundamental physical constants: 2018",
28
+ # Rev. Mod. Phys. 93, 025010 (2021); https://physics.nist.gov/constants
29
+ # h, e, and k_B are exact by the 2019 SI redefinition (BIPM, 9th SI
30
+ # brochure, 2019); BK above is that exact k_B/e value to 10 significant
31
+ # figures and is shared by the classic (NJOY-reproducing) path.
32
+ # Derived values are computed from the defining constants rather than
33
+ # hardcoded, so they cannot drift out of sync.
34
+ from math import pi as _pi
35
+
36
+ PLANCK_J_S = 6.62607015e-34 # h [J s] (exact, SI 2019)
37
+ ECHARGE_C = 1.602176634e-19 # e [C] (exact, SI 2019)
38
+ KB_J_PER_K = 1.380649e-23 # k_B [J/K] (exact, SI 2019)
39
+ HBAR_J_S = PLANCK_J_S / (2.0 * _pi) # ħ [J s]
40
+ HBAR_EV_S = HBAR_J_S / ECHARGE_C # ħ [eV s] = 6.582119569...e-16
41
+ NEUTRON_MASS_KG = 1.67492749804e-27 # m_n [kg] (CODATA 2018)
42
+ AMU_KG = 1.66053906660e-27 # m_u [kg] (CODATA 2018)
43
+
44
+ # 1 THz in eV: h * 1e12 / e = 4.135667696...e-3 eV
45
+ THZ_TO_EV = PLANCK_J_S * 1.0e12 / ECHARGE_C
46
+
47
+ # ħ²/(2 m_n) in meV·Å² = 2.0721248551... (the free-neutron kinematic
48
+ # constant linking alpha to Q²: E[meV] = HBAR2_OVER_2MN_MEV_A2 · Q[1/Å]²)
49
+ HBAR2_OVER_2MN_MEV_A2 = (
50
+ HBAR_J_S ** 2 / (2.0 * NEUTRON_MASS_KG) / (ECHARGE_C * 1.0e-3) * 1.0e20
51
+ )
52
+
53
+ # ============================================================================
54
+ # Phonon-mode floor policy (mesh-sum numerical guards, modes 1/2)
55
+ # ============================================================================
56
+ # Per-mode one-phonon amplitudes and thermal displacements diverge as
57
+ # kT/ω² for ω → 0. In the exact BZ integral the acoustic divergence is
58
+ # integrable, but on a DISCRETE mesh the three Goldstone modes at Γ carry
59
+ # finite weight with a numerically meaningless ω (acoustic-sum-rule
60
+ # noise, commonly up to ~0.05 meV), so they must be excluded outright.
61
+ # Away from Γ every mode is physical and only an overflow guard is
62
+ # needed. Hence a two-tier rule:
63
+ # - at Γ q-points: drop modes below GAMMA_ACOUSTIC_FLOOR_MEV
64
+ # (optical Γ modes pass; Goldstone noise cannot);
65
+ # - elsewhere: drop modes below MODE_ENERGY_FLOOR_MEV (1 μeV), far
66
+ # under any acoustic energy at a nonzero mesh q-point, so genuinely
67
+ # ultra-soft physics is kept.
68
+ # Shifted Monkhorst-Pack production meshes contain no Γ point and no
69
+ # sub-μeV modes, so both guards are inert there.
70
+ MODE_ENERGY_FLOOR_MEV = 1.0e-3
71
+ GAMMA_ACOUSTIC_FLOOR_MEV = 0.1
72
+
73
+ _Z_TO_SYMBOL = {
74
+ 1: 'H', 2: 'He', 3: 'Li', 4: 'Be', 5: 'B', 6: 'C', 7: 'N', 8: 'O',
75
+ 9: 'F', 10: 'Ne',11: 'Na',12: 'Mg',13: 'Al',14: 'Si',15: 'P', 16: 'S',
76
+ 17: 'Cl',18: 'Ar',19: 'K', 20: 'Ca',21: 'Sc',22: 'Ti',23: 'V', 24: 'Cr',
77
+ 25: 'Mn',26: 'Fe',27: 'Co',28: 'Ni',29: 'Cu',30: 'Zn',31: 'Ga',32: 'Ge',
78
+ 33: 'As',34: 'Se',35: 'Br',36: 'Kr',37: 'Rb',38: 'Sr',39: 'Y', 40: 'Zr',
79
+ 41: 'Nb',42: 'Mo',43: 'Tc',44: 'Ru',45: 'Rh',46: 'Pd',47: 'Ag',48: 'Cd',
80
+ 49: 'In',50: 'Sn',51: 'Sb',52: 'Te',53: 'I', 54: 'Xe',55: 'Cs',56: 'Ba',
81
+ 57: 'La',58: 'Ce',59: 'Pr',60: 'Nd',61: 'Pm',62: 'Sm',63: 'Eu',64: 'Gd',
82
+ 65: 'Tb',66: 'Dy',67: 'Ho',68: 'Er',69: 'Tm',70: 'Yb',71: 'Lu',72: 'Hf',
83
+ 73: 'Ta',74: 'W', 75: 'Re',76: 'Os',77: 'Ir',78: 'Pt',79: 'Au',80: 'Hg',
84
+ 81: 'Tl',82: 'Pb',83: 'Bi',84: 'Po',85: 'At',86: 'Rn',87: 'Fr',88: 'Ra',
85
+ 89: 'Ac',90: 'Th',91: 'Pa',92: 'U', 93: 'Np',94: 'Pu',95: 'Am',96: 'Cm',
86
+ 97: 'Bk',98: 'Cf',99: 'Es',100:'Fm',101:'Md',102:'No',103:'Lr',
87
+ }
88
+