alphanso 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.
- alphanso/__init__.py +9 -0
- alphanso/__main__.py +366 -0
- alphanso/atomic_data_loader.py +390 -0
- alphanso/constants.py +11 -0
- alphanso/data/atomic_data/atomic_data.json +38472 -0
- alphanso/data/atomic_data/atomic_data_loader.py +536 -0
- alphanso/data/atomic_data/element_symbols.json +14 -0
- alphanso/data/atomic_data/metadata.json +15 -0
- alphanso/data/atomic_data/natural_abundances.json +1581 -0
- alphanso/data/sources_overrides.yaml +19 -0
- alphanso/data_manager.py +206 -0
- alphanso/parsers.py +1861 -0
- alphanso/sources_parsers.py +681 -0
- alphanso/transport.py +2150 -0
- alphanso/utils.py +215 -0
- alphanso-1.0.0.dist-info/METADATA +396 -0
- alphanso-1.0.0.dist-info/RECORD +21 -0
- alphanso-1.0.0.dist-info/WHEEL +5 -0
- alphanso-1.0.0.dist-info/entry_points.txt +2 -0
- alphanso-1.0.0.dist-info/licenses/LICENSE +28 -0
- alphanso-1.0.0.dist-info/top_level.txt +1 -0
alphanso/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Alphanso package initialization
|
|
2
|
+
|
|
3
|
+
__version__ = "1.0.0"
|
|
4
|
+
|
|
5
|
+
from .transport import Transport
|
|
6
|
+
from .data_manager import ensure_data, get_data_dir, is_data_available, DATA_VERSION
|
|
7
|
+
|
|
8
|
+
__all__ = ['Transport', '__version__', 'ensure_data', 'get_data_dir',
|
|
9
|
+
'is_data_available', 'DATA_VERSION']
|
alphanso/__main__.py
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Main entry point for the alphanso package.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
alphanso <config_path> [output_dir] # backwards-compatible (implicit run)
|
|
7
|
+
alphanso run <config_path> [output_dir] # explicit run
|
|
8
|
+
alphanso download-data [--dir PATH] # pre-download data
|
|
9
|
+
alphanso data-info # show data paths and status
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
import yaml
|
|
17
|
+
import numpy as np
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Dict, List, Union, Any
|
|
20
|
+
from collections import defaultdict
|
|
21
|
+
|
|
22
|
+
from .transport import Transport
|
|
23
|
+
from .data_manager import ensure_data, get_data_info
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
_SUBCOMMANDS = {"run", "download-data", "data-info"}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def read_in(config_path: Union[str, Path]) -> List[Dict[str, Any]]:
|
|
31
|
+
"""
|
|
32
|
+
Read configuration from a YAML file or directory of YAML files.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
config_path: Path to YAML file or directory containing YAML files
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
List of configuration dictionaries, each with a 'source' key indicating the source file
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
FileNotFoundError: If config_path doesn't exist
|
|
42
|
+
yaml.YAMLError: If YAML parsing fails
|
|
43
|
+
ValueError: If no valid YAML files found
|
|
44
|
+
"""
|
|
45
|
+
config_path = Path(config_path)
|
|
46
|
+
|
|
47
|
+
if not config_path.exists():
|
|
48
|
+
raise FileNotFoundError(
|
|
49
|
+
f"Configuration path does not exist: {config_path}")
|
|
50
|
+
|
|
51
|
+
configs = []
|
|
52
|
+
|
|
53
|
+
if config_path.is_file():
|
|
54
|
+
try:
|
|
55
|
+
with open(config_path, 'r') as f:
|
|
56
|
+
yaml_content = yaml.safe_load(f)
|
|
57
|
+
|
|
58
|
+
if yaml_content is None:
|
|
59
|
+
raise ValueError(f"Empty YAML file: {config_path}")
|
|
60
|
+
|
|
61
|
+
if isinstance(yaml_content, list):
|
|
62
|
+
for i, config in enumerate(yaml_content):
|
|
63
|
+
if isinstance(config, dict):
|
|
64
|
+
config['source'] = f"{config_path.stem}_{i+1}"
|
|
65
|
+
configs.append(config)
|
|
66
|
+
elif isinstance(yaml_content, dict):
|
|
67
|
+
yaml_content['source'] = config_path.stem
|
|
68
|
+
configs.append(yaml_content)
|
|
69
|
+
else:
|
|
70
|
+
raise ValueError(
|
|
71
|
+
f"Invalid YAML content in {config_path}: expected dict or list of dicts")
|
|
72
|
+
|
|
73
|
+
except yaml.YAMLError as e:
|
|
74
|
+
raise yaml.YAMLError(
|
|
75
|
+
f"Failed to parse YAML file {config_path}: {e}")
|
|
76
|
+
|
|
77
|
+
elif config_path.is_dir():
|
|
78
|
+
yaml_files = list(config_path.glob("*.yaml")) + \
|
|
79
|
+
list(config_path.glob("*.yml"))
|
|
80
|
+
|
|
81
|
+
if not yaml_files:
|
|
82
|
+
raise ValueError(
|
|
83
|
+
f"No YAML files found in directory: {config_path}")
|
|
84
|
+
|
|
85
|
+
for yaml_file in sorted(yaml_files):
|
|
86
|
+
try:
|
|
87
|
+
with open(yaml_file, 'r') as f:
|
|
88
|
+
yaml_content = yaml.safe_load(f)
|
|
89
|
+
|
|
90
|
+
if yaml_content is None:
|
|
91
|
+
print(f"Warning: Empty YAML file: {yaml_file}")
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
if isinstance(yaml_content, list):
|
|
95
|
+
for i, config in enumerate(yaml_content):
|
|
96
|
+
if isinstance(config, dict):
|
|
97
|
+
config['source'] = f"{yaml_file.stem}_{i+1}"
|
|
98
|
+
configs.append(config)
|
|
99
|
+
elif isinstance(yaml_content, dict):
|
|
100
|
+
yaml_content['source'] = yaml_file.stem
|
|
101
|
+
configs.append(yaml_content)
|
|
102
|
+
else:
|
|
103
|
+
print(
|
|
104
|
+
f"Warning: Invalid YAML content in {yaml_file}: expected dict or list of dicts")
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
except yaml.YAMLError as e:
|
|
108
|
+
print(f"Warning: Failed to parse YAML file {yaml_file}: {e}")
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
else:
|
|
112
|
+
raise ValueError(
|
|
113
|
+
f"Invalid config path: {config_path} is neither a file nor directory")
|
|
114
|
+
|
|
115
|
+
if not configs:
|
|
116
|
+
raise ValueError(f"No valid configurations found in {config_path}")
|
|
117
|
+
|
|
118
|
+
print(f"Loaded {len(configs)} configuration(s) from {config_path}")
|
|
119
|
+
return configs
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def read_out(configs: List[Dict[str, Any]],
|
|
123
|
+
output_dir: Union[str, Path] = None) -> Path:
|
|
124
|
+
"""
|
|
125
|
+
Set up output directory structure for the configurations.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
configs: List of configuration dictionaries
|
|
129
|
+
output_dir: Optional output directory path (default: creates alphanso_output/)
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Path to the created output directory
|
|
133
|
+
|
|
134
|
+
Raises:
|
|
135
|
+
OSError: If output directory creation fails
|
|
136
|
+
"""
|
|
137
|
+
if output_dir is None:
|
|
138
|
+
output_dir = Path("alphanso_output")
|
|
139
|
+
else:
|
|
140
|
+
output_dir = Path(output_dir)
|
|
141
|
+
|
|
142
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
143
|
+
|
|
144
|
+
source_groups = defaultdict(list)
|
|
145
|
+
for config in configs:
|
|
146
|
+
source = config.get('source', 'unknown')
|
|
147
|
+
source_groups[source].append(config)
|
|
148
|
+
|
|
149
|
+
for source, config_list in source_groups.items():
|
|
150
|
+
source_dir = output_dir / source
|
|
151
|
+
source_dir.mkdir(exist_ok=True)
|
|
152
|
+
|
|
153
|
+
if len(config_list) == 1:
|
|
154
|
+
output_file = source_dir / "output.yaml"
|
|
155
|
+
with open(output_file, 'w') as f:
|
|
156
|
+
yaml.dump(config_list[0], f, default_flow_style=True, indent=2)
|
|
157
|
+
|
|
158
|
+
if '_result' in config_list[0]:
|
|
159
|
+
results_file = source_dir / "results.yaml"
|
|
160
|
+
with open(results_file, 'w') as f:
|
|
161
|
+
yaml.dump(
|
|
162
|
+
config_list[0]['_result'],
|
|
163
|
+
f,
|
|
164
|
+
default_flow_style=True,
|
|
165
|
+
indent=2)
|
|
166
|
+
else:
|
|
167
|
+
for i, config in enumerate(config_list):
|
|
168
|
+
output_file = source_dir / f"output_{i+1}.yaml"
|
|
169
|
+
with open(output_file, 'w') as f:
|
|
170
|
+
yaml.dump(config, f, default_flow_style=True, indent=2)
|
|
171
|
+
|
|
172
|
+
if '_result' in config:
|
|
173
|
+
results_file = source_dir / f"results_{i+1}.yaml"
|
|
174
|
+
with open(results_file, 'w') as f:
|
|
175
|
+
yaml.dump(
|
|
176
|
+
config['_result'],
|
|
177
|
+
f,
|
|
178
|
+
default_flow_style=True,
|
|
179
|
+
indent=2)
|
|
180
|
+
|
|
181
|
+
print(f"Created output directory structure: {output_dir}")
|
|
182
|
+
return output_dir
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _cmd_run(config_path, output_dir):
|
|
186
|
+
"""Execute the run subcommand (calculation)."""
|
|
187
|
+
ensure_data()
|
|
188
|
+
|
|
189
|
+
configs = read_in(config_path)
|
|
190
|
+
|
|
191
|
+
for i, config in enumerate(configs):
|
|
192
|
+
source = config.get('source', f'config_{i+1}')
|
|
193
|
+
config_id = config.get('id', config.get('name', f'config_{i+1}'))
|
|
194
|
+
|
|
195
|
+
print(
|
|
196
|
+
f"\nProcessing configuration {i+1}/{len(configs)}: {config_id}")
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
required_fields = ['calc_type']
|
|
200
|
+
missing_fields = [
|
|
201
|
+
field for field in required_fields if field not in config]
|
|
202
|
+
|
|
203
|
+
if missing_fields:
|
|
204
|
+
raise ValueError(
|
|
205
|
+
f"Configuration {config_id} missing required fields: {missing_fields}")
|
|
206
|
+
|
|
207
|
+
calc_type = config.get('calc_type')
|
|
208
|
+
if calc_type not in [
|
|
209
|
+
'beam',
|
|
210
|
+
'homogeneous',
|
|
211
|
+
'interface',
|
|
212
|
+
'sandwich']:
|
|
213
|
+
raise ValueError(
|
|
214
|
+
f"Configuration {config_id} has invalid calc_type: {calc_type}. Must be 'beam', 'homogeneous', 'interface', or 'sandwich'")
|
|
215
|
+
|
|
216
|
+
if calc_type == 'beam':
|
|
217
|
+
if 'beam_energy' not in config and 'beam_intensities' not in config:
|
|
218
|
+
raise ValueError(
|
|
219
|
+
f"Beam calculation {config_id} missing required field: beam_energy or beam_intensities")
|
|
220
|
+
elif calc_type == 'interface':
|
|
221
|
+
if 'source_matdef' not in config or 'target_matdef' not in config:
|
|
222
|
+
raise ValueError(
|
|
223
|
+
f"Interface calculation {config_id} missing required field: source_matdef or target_matdef")
|
|
224
|
+
elif calc_type == 'sandwich':
|
|
225
|
+
required = [
|
|
226
|
+
'source_matdef',
|
|
227
|
+
'source_density',
|
|
228
|
+
'target_matdef']
|
|
229
|
+
missing = [f for f in required if f not in config]
|
|
230
|
+
if missing:
|
|
231
|
+
raise ValueError(
|
|
232
|
+
f"Sandwich calculation {config_id} missing required fields: {missing}")
|
|
233
|
+
|
|
234
|
+
has_layers_list = 'intermediate_layers' in config
|
|
235
|
+
has_single_layer = all(
|
|
236
|
+
k in config for k in [
|
|
237
|
+
'intermediate_matdef',
|
|
238
|
+
'intermediate_density',
|
|
239
|
+
'intermediate_thickness'])
|
|
240
|
+
|
|
241
|
+
if not (has_layers_list or has_single_layer):
|
|
242
|
+
raise ValueError(
|
|
243
|
+
f"Sandwich calculation {config_id} requires either 'intermediate_layers' (list) "
|
|
244
|
+
"or 'intermediate_matdef', 'intermediate_density', 'intermediate_thickness' (single layer)")
|
|
245
|
+
|
|
246
|
+
if has_single_layer and not has_layers_list:
|
|
247
|
+
config['intermediate_layers'] = [{
|
|
248
|
+
'matdef': config.pop('intermediate_matdef'),
|
|
249
|
+
'density': config.pop('intermediate_density'),
|
|
250
|
+
'thickness': config.pop('intermediate_thickness'),
|
|
251
|
+
'include_targets': config.get('include_intermediate_targets', True)
|
|
252
|
+
}]
|
|
253
|
+
|
|
254
|
+
for j, layer in enumerate(config['intermediate_layers']):
|
|
255
|
+
if 'matdef' not in layer or 'density' not in layer or 'thickness' not in layer:
|
|
256
|
+
raise ValueError(
|
|
257
|
+
f"Sandwich calculation {config_id} layer {j} missing required fields: matdef, density, thickness")
|
|
258
|
+
if layer['thickness'] <= 0:
|
|
259
|
+
raise ValueError(
|
|
260
|
+
f"Sandwich calculation {config_id} layer {j} thickness must be positive, got {layer['thickness']}")
|
|
261
|
+
if layer['density'] <= 0:
|
|
262
|
+
raise ValueError(
|
|
263
|
+
f"Sandwich calculation {config_id} layer {j} density must be positive, got {layer['density']}")
|
|
264
|
+
|
|
265
|
+
result = Transport.calculate(config)
|
|
266
|
+
logger.info(f"Completed {config_id}")
|
|
267
|
+
|
|
268
|
+
config['_result'] = result
|
|
269
|
+
|
|
270
|
+
except Exception as e:
|
|
271
|
+
print(f"Error processing {config_id}: {e}")
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
output_dir_path = read_out(configs, output_dir)
|
|
275
|
+
print(f"Output saved to: {output_dir_path}")
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _cmd_download_data(target_dir):
|
|
279
|
+
"""Execute the download-data subcommand."""
|
|
280
|
+
if target_dir:
|
|
281
|
+
os.environ["ALPHANSO_DATA_DIR"] = target_dir
|
|
282
|
+
data_path = ensure_data()
|
|
283
|
+
print(f"Nuclear data ready at: {data_path}")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _cmd_data_info():
|
|
287
|
+
"""Execute the data-info subcommand."""
|
|
288
|
+
info = get_data_info()
|
|
289
|
+
print("ALPHANSO Data Info")
|
|
290
|
+
print("=" * 40)
|
|
291
|
+
for key, value in info.items():
|
|
292
|
+
print(f" {key}: {value}")
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def main():
|
|
296
|
+
"""Main entry point for the alphanso package."""
|
|
297
|
+
# Backwards compatibility: if the first positional arg is not a known
|
|
298
|
+
# subcommand, treat the entire invocation as an implicit "run".
|
|
299
|
+
raw_args = sys.argv[1:]
|
|
300
|
+
if raw_args and raw_args[0] not in _SUBCOMMANDS and not raw_args[0].startswith("-"):
|
|
301
|
+
# Implicit run: alphanso config.yaml [output_dir]
|
|
302
|
+
raw_args = ["run"] + raw_args
|
|
303
|
+
|
|
304
|
+
parser = argparse.ArgumentParser(
|
|
305
|
+
description="ALPHANSO: Alpha-Neutron Source calculations",
|
|
306
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
307
|
+
epilog="""
|
|
308
|
+
Examples:
|
|
309
|
+
alphanso config.yaml # Run with config (implicit run)
|
|
310
|
+
alphanso run config.yaml # Explicit run
|
|
311
|
+
alphanso run config.yaml results/ # Run with custom output dir
|
|
312
|
+
alphanso download-data # Pre-download nuclear data
|
|
313
|
+
alphanso download-data --dir /custom/path # Download to custom location
|
|
314
|
+
alphanso data-info # Show data paths and status
|
|
315
|
+
"""
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
319
|
+
|
|
320
|
+
# run subcommand
|
|
321
|
+
run_parser = subparsers.add_parser(
|
|
322
|
+
"run", help="Run ALPHANSO calculation(s)")
|
|
323
|
+
run_parser.add_argument(
|
|
324
|
+
"config_path",
|
|
325
|
+
help="Path to YAML config file or directory containing YAML files"
|
|
326
|
+
)
|
|
327
|
+
run_parser.add_argument(
|
|
328
|
+
"output_dir",
|
|
329
|
+
nargs="?",
|
|
330
|
+
help="Output directory (default: alphanso_output/)"
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# download-data subcommand
|
|
334
|
+
dl_parser = subparsers.add_parser(
|
|
335
|
+
"download-data", help="Download nuclear data files")
|
|
336
|
+
dl_parser.add_argument(
|
|
337
|
+
"--dir",
|
|
338
|
+
dest="target_dir",
|
|
339
|
+
default=None,
|
|
340
|
+
help="Target directory for data download"
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# data-info subcommand
|
|
344
|
+
subparsers.add_parser(
|
|
345
|
+
"data-info", help="Show nuclear data paths and status")
|
|
346
|
+
|
|
347
|
+
args = parser.parse_args(raw_args)
|
|
348
|
+
|
|
349
|
+
if args.command is None:
|
|
350
|
+
parser.print_help()
|
|
351
|
+
sys.exit(1)
|
|
352
|
+
|
|
353
|
+
try:
|
|
354
|
+
if args.command == "run":
|
|
355
|
+
_cmd_run(args.config_path, args.output_dir)
|
|
356
|
+
elif args.command == "download-data":
|
|
357
|
+
_cmd_download_data(args.target_dir)
|
|
358
|
+
elif args.command == "data-info":
|
|
359
|
+
_cmd_data_info()
|
|
360
|
+
except Exception as e:
|
|
361
|
+
print(f"Fatal error: {e}")
|
|
362
|
+
sys.exit(1)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
if __name__ == "__main__":
|
|
366
|
+
main()
|