fusionflux 0.2.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.
- fusionflux/__init__.py +50 -0
- fusionflux/__main__.py +22 -0
- fusionflux/cli.py +134 -0
- fusionflux/predictor.json +182 -0
- fusionflux/predictor.py +667 -0
- fusionflux-0.2.2.dist-info/METADATA +405 -0
- fusionflux-0.2.2.dist-info/RECORD +11 -0
- fusionflux-0.2.2.dist-info/WHEEL +5 -0
- fusionflux-0.2.2.dist-info/entry_points.txt +2 -0
- fusionflux-0.2.2.dist-info/licenses/LICENSE +21 -0
- fusionflux-0.2.2.dist-info/top_level.txt +1 -0
fusionflux/__init__.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""The confinement study, callable.
|
|
2
|
+
|
|
3
|
+
``pip install fusionflux`` installs exactly two things: this package and
|
|
4
|
+
``neutron_yield``. It deliberately does not install the analysis scripts. Those
|
|
5
|
+
are ``hdb5.py``, ``scaling_law.py``, ``dimensional.py`` and the twelve
|
|
6
|
+
``analysis_*.py`` modules, they are run from a checkout as ``python3 hdb5.py
|
|
7
|
+
train``, and every one of them needs a dataset that is fetched from OSF rather
|
|
8
|
+
than shipped. Installing them would put ``config``, ``storage``, ``validation``
|
|
9
|
+
and ``forecast`` on the import path of every environment this package lands in,
|
|
10
|
+
where they would shadow any other project's module of the same name, in exchange
|
|
11
|
+
for scripts that cannot run without a download the wheel does not provide.
|
|
12
|
+
|
|
13
|
+
So the installable surface is the part that works with nothing but the wheel: a
|
|
14
|
+
point estimate, a calibrated interval, an extrapolation distance, and a refusal,
|
|
15
|
+
all read from a few kilobytes of coefficients shipped inside the package.
|
|
16
|
+
|
|
17
|
+
from fusionflux import predict
|
|
18
|
+
|
|
19
|
+
result = predict(ip_ma=15.0, bt_t=5.3, ne_line_1e19_m3=10.0, p_loss_mw=87.0,
|
|
20
|
+
r_m=6.2, inverse_aspect_ratio=0.3226, kappa=1.7, m_eff_amu=2.5)
|
|
21
|
+
|
|
22
|
+
result.tau_s # 2.837
|
|
23
|
+
result.physics_exceeds_training_ceiling # True
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from fusionflux.predictor import (
|
|
29
|
+
ConfinementPrediction,
|
|
30
|
+
ModelPrediction,
|
|
31
|
+
ServiceCard,
|
|
32
|
+
build_service_card,
|
|
33
|
+
format_prediction,
|
|
34
|
+
load_card,
|
|
35
|
+
predict,
|
|
36
|
+
save_card,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"ConfinementPrediction",
|
|
41
|
+
"ModelPrediction",
|
|
42
|
+
"ServiceCard",
|
|
43
|
+
"build_service_card",
|
|
44
|
+
"format_prediction",
|
|
45
|
+
"load_card",
|
|
46
|
+
"predict",
|
|
47
|
+
"save_card",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
__version__ = "0.2.1"
|
fusionflux/__main__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""``python3 -m fusionflux`` runs the same command as the console script.
|
|
2
|
+
|
|
3
|
+
Useful in two places. In a checkout there is no installed console script until
|
|
4
|
+
someone runs `pip install -e .`, and `make results` should not depend on that.
|
|
5
|
+
And in an environment where the wheel is installed but its scripts directory is
|
|
6
|
+
not on ``PATH``, this reaches the same entry point through the interpreter that
|
|
7
|
+
imported it.
|
|
8
|
+
|
|
9
|
+
``python3 -m fusionflux.predictor build`` still works and is what the card
|
|
10
|
+
builder's own ``main`` is for, but it warns: importing this package imports
|
|
11
|
+
``fusionflux.predictor`` eagerly, so runpy finds it in ``sys.modules`` before it
|
|
12
|
+
executes it. Going through this module avoids that.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
from fusionflux.cli import main
|
|
20
|
+
|
|
21
|
+
if __name__ == "__main__":
|
|
22
|
+
main(sys.argv[1:])
|
fusionflux/cli.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""The ``fusionflux`` command: the confinement study first, the demo behind it.
|
|
2
|
+
|
|
3
|
+
``pip install fusionflux`` used to give one console command, and it ran the
|
|
4
|
+
synthetic neutron-yield pipeline. That pipeline is careful engineering and it
|
|
5
|
+
supports no scientific claim; ``README.md`` says so in its own section, and its
|
|
6
|
+
dataset is generated from a hand-written signal. So the single thing the package
|
|
7
|
+
offered a new user was the one part of it that measures nothing.
|
|
8
|
+
|
|
9
|
+
This module makes the front door the study:
|
|
10
|
+
|
|
11
|
+
fusionflux predict ... an energy confinement time, with an interval, an
|
|
12
|
+
extrapolation distance, and a refusal when the
|
|
13
|
+
operating point is beyond anything measured here
|
|
14
|
+
fusionflux card rebuild ``results/predictor.json``, which is what
|
|
15
|
+
``predict`` reads
|
|
16
|
+
fusionflux neutron ... the synthetic pipeline, unchanged, one level down
|
|
17
|
+
(checkout only: the wheel does not install it)
|
|
18
|
+
|
|
19
|
+
The ``neutron`` subcommand delegates to ``neutron_yield.fusionflux_cli`` rather
|
|
20
|
+
than reimplementing it, so that pipeline's arguments, defaults and behaviour are
|
|
21
|
+
defined in exactly one place and cannot drift from the tests that cover them.
|
|
22
|
+
Calling that module directly still works and is still tested; this only changes
|
|
23
|
+
which command a fresh install puts on the path.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import json
|
|
30
|
+
import sys
|
|
31
|
+
|
|
32
|
+
from fusionflux import predictor
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
36
|
+
parser = argparse.ArgumentParser(
|
|
37
|
+
prog="fusionflux",
|
|
38
|
+
description=(
|
|
39
|
+
"Tokamak energy confinement time: predict an operating point, or rebuild "
|
|
40
|
+
"the predictor card. The synthetic neutron-yield demo lives under `neutron`."
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
44
|
+
|
|
45
|
+
predict_parser = subparsers.add_parser(
|
|
46
|
+
"predict",
|
|
47
|
+
help="Predict confinement time, with an interval and an out-of-range refusal.",
|
|
48
|
+
description=(
|
|
49
|
+
"Predicts energy confinement time for one operating point. Reports the "
|
|
50
|
+
"extrapolation distance, a calibrated interval, and an explicit warning when "
|
|
51
|
+
"the point sits beyond what this study measured, including when no "
|
|
52
|
+
"range-bounded model can reach the answer at all."
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
for name in predictor.REQUIRED_INPUTS:
|
|
56
|
+
predict_parser.add_argument(
|
|
57
|
+
f"--{name.replace('_', '-')}",
|
|
58
|
+
type=float,
|
|
59
|
+
required=True,
|
|
60
|
+
help=f"{name} (finite and strictly positive)",
|
|
61
|
+
)
|
|
62
|
+
predict_parser.add_argument(
|
|
63
|
+
"--card",
|
|
64
|
+
default=str(predictor.DEFAULT_CARD_PATH),
|
|
65
|
+
help="Predictor card to read (default: results/predictor.json).",
|
|
66
|
+
)
|
|
67
|
+
predict_parser.add_argument(
|
|
68
|
+
"--json", action="store_true", help="Emit JSON instead of the readable report."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
card_parser = subparsers.add_parser(
|
|
72
|
+
"card", help="Rebuild the predictor card from the pinned dataset."
|
|
73
|
+
)
|
|
74
|
+
card_parser.add_argument("--output", default=str(predictor.DEFAULT_CARD_PATH))
|
|
75
|
+
|
|
76
|
+
# ``REMAINDER`` so the delegated parser owns its own flags entirely: without
|
|
77
|
+
# it, an option this parser happens to define too would be captured here and
|
|
78
|
+
# never reach the pipeline that defines its meaning.
|
|
79
|
+
neutron_parser = subparsers.add_parser(
|
|
80
|
+
"neutron",
|
|
81
|
+
help="The synthetic neutron-yield demo pipeline (supports no scientific claim).",
|
|
82
|
+
description=(
|
|
83
|
+
"Delegates to neutron_yield.fusionflux_cli. This pipeline trains on "
|
|
84
|
+
"synthetic data generated from a hand-written signal, so its accuracy "
|
|
85
|
+
"numbers measure how learnable that generator is and nothing else."
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
neutron_parser.add_argument(
|
|
89
|
+
"args", nargs=argparse.REMAINDER, help="Arguments passed through unchanged."
|
|
90
|
+
)
|
|
91
|
+
return parser
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def main(argv: list[str] | None = None) -> None:
|
|
95
|
+
args = build_parser().parse_args(argv)
|
|
96
|
+
|
|
97
|
+
if args.command == "card":
|
|
98
|
+
print(f"wrote {predictor.save_card(predictor.build_service_card(), args.output)}")
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
if args.command == "neutron":
|
|
102
|
+
# Checkout-only, and not as a demotion. `neutron_yield` reaches its data
|
|
103
|
+
# directories through `config.PROJECT_ROOT`, which is derived from that
|
|
104
|
+
# module's own location, so an installed copy would resolve them inside
|
|
105
|
+
# site-packages and write training runs there. The wheel therefore does
|
|
106
|
+
# not ship it, and this says so instead of surfacing a bare import error.
|
|
107
|
+
try:
|
|
108
|
+
from neutron_yield.fusionflux_cli import main as neutron_main
|
|
109
|
+
except ModuleNotFoundError as error:
|
|
110
|
+
raise SystemExit(
|
|
111
|
+
"The neutron-yield pipeline is not installed by the wheel "
|
|
112
|
+
f"({error.name} is missing). It resolves its data directories "
|
|
113
|
+
"relative to its own source file, so an installed copy would read "
|
|
114
|
+
"and write inside site-packages. Clone the repository and run it "
|
|
115
|
+
"from there: see docs/neutron-yield-pipeline.md. Nothing in that "
|
|
116
|
+
"pipeline supports a scientific claim; `fusionflux predict` is the "
|
|
117
|
+
"study and it needs no checkout."
|
|
118
|
+
) from error
|
|
119
|
+
|
|
120
|
+
neutron_main(args.args)
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
result = predictor.predict(
|
|
124
|
+
**{name: getattr(args, name) for name in predictor.REQUIRED_INPUTS},
|
|
125
|
+
card=predictor.load_card(args.card),
|
|
126
|
+
)
|
|
127
|
+
if args.json:
|
|
128
|
+
print(json.dumps(result.to_json(), indent=2))
|
|
129
|
+
else:
|
|
130
|
+
print(predictor.format_prediction(result))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
main(sys.argv[1:])
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
{
|
|
2
|
+
"generated_on": "2026-09-01",
|
|
3
|
+
"dataset_sha256": "67601c2da5c51f90cf6298ff499cccc74d09ac80c2b98c7dde0d8db3ebb9ac5b",
|
|
4
|
+
"n_training_rows": 6228,
|
|
5
|
+
"nominal_coverage": 0.9,
|
|
6
|
+
"model_feature_columns": [
|
|
7
|
+
"log_ip_ma",
|
|
8
|
+
"log_bt_t",
|
|
9
|
+
"log_ne_line_1e19_m3",
|
|
10
|
+
"log_p_loss_mw",
|
|
11
|
+
"log_r_m",
|
|
12
|
+
"log_inverse_aspect_ratio",
|
|
13
|
+
"log_kappa",
|
|
14
|
+
"log_m_eff_amu"
|
|
15
|
+
],
|
|
16
|
+
"distance_feature_columns": [
|
|
17
|
+
"log_ip_ma",
|
|
18
|
+
"log_bt_t",
|
|
19
|
+
"log_ne_line_1e19_m3",
|
|
20
|
+
"log_p_loss_mw",
|
|
21
|
+
"log_r_m",
|
|
22
|
+
"log_kappa",
|
|
23
|
+
"log_inverse_aspect_ratio",
|
|
24
|
+
"log_m_eff_amu",
|
|
25
|
+
"log_a_m"
|
|
26
|
+
],
|
|
27
|
+
"coefficients": {
|
|
28
|
+
"powerlaw_collisionless": [
|
|
29
|
+
-2.9016484433390097,
|
|
30
|
+
1.0588378196767754,
|
|
31
|
+
0.1357600483267104,
|
|
32
|
+
0.27058436642128914,
|
|
33
|
+
-0.7315326226678261,
|
|
34
|
+
1.676928781169285,
|
|
35
|
+
0.2555387339049824,
|
|
36
|
+
0.9443168653524817,
|
|
37
|
+
0.24314374146842235
|
|
38
|
+
],
|
|
39
|
+
"powerlaw_free": [
|
|
40
|
+
-2.9038356312016065,
|
|
41
|
+
1.0804315034258738,
|
|
42
|
+
0.12016001694501567,
|
|
43
|
+
0.2166326537331674,
|
|
44
|
+
-0.688387425406081,
|
|
45
|
+
1.5791341035779596,
|
|
46
|
+
0.16673071620488358,
|
|
47
|
+
0.8975166175406656,
|
|
48
|
+
0.25641042610379533
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"interval_quantile": {
|
|
52
|
+
"powerlaw_collisionless": 2.838376490527002,
|
|
53
|
+
"powerlaw_free": 2.7837281921196824,
|
|
54
|
+
"ipb98y2_analytic": 3.212461708896956
|
|
55
|
+
},
|
|
56
|
+
"interval_distance_intercept": {
|
|
57
|
+
"powerlaw_collisionless": -2.045114888173274,
|
|
58
|
+
"powerlaw_free": -2.022631434539392,
|
|
59
|
+
"ipb98y2_analytic": -2.1294395081165685
|
|
60
|
+
},
|
|
61
|
+
"interval_distance_slope": {
|
|
62
|
+
"powerlaw_collisionless": -0.04025105215351508,
|
|
63
|
+
"powerlaw_free": -0.02748556544631119,
|
|
64
|
+
"ipb98y2_analytic": -0.050129759368343886
|
|
65
|
+
},
|
|
66
|
+
"distance_mean": [
|
|
67
|
+
0.14637248941480596,
|
|
68
|
+
0.6980441044410912,
|
|
69
|
+
1.6826003273110546,
|
|
70
|
+
1.8418947236029202,
|
|
71
|
+
0.7096079515122506,
|
|
72
|
+
0.43608025361888186,
|
|
73
|
+
-1.155303965685298,
|
|
74
|
+
0.6354658124628183,
|
|
75
|
+
-0.4456960141730473
|
|
76
|
+
],
|
|
77
|
+
"distance_precision": [
|
|
78
|
+
[
|
|
79
|
+
41.50331457568594,
|
|
80
|
+
-26.270515549815787,
|
|
81
|
+
-8.5638131557017,
|
|
82
|
+
0.5470758008226411,
|
|
83
|
+
-10.39133151690429,
|
|
84
|
+
-38.71154103482346,
|
|
85
|
+
-33.4609893814481,
|
|
86
|
+
-0.3955310086525973,
|
|
87
|
+
-43.852320898356254
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
-26.270515549815833,
|
|
91
|
+
33.03633313828385,
|
|
92
|
+
1.1735772646195255,
|
|
93
|
+
-3.6955217561400957,
|
|
94
|
+
-0.2518686768868297,
|
|
95
|
+
37.857140738093285,
|
|
96
|
+
32.56053885161475,
|
|
97
|
+
-8.208930061399926,
|
|
98
|
+
32.308670174731304
|
|
99
|
+
],
|
|
100
|
+
[
|
|
101
|
+
-8.563813155701732,
|
|
102
|
+
1.1735772646195504,
|
|
103
|
+
14.004375935379445,
|
|
104
|
+
-4.0502627274009235,
|
|
105
|
+
8.94591684120678,
|
|
106
|
+
1.6797355231156272,
|
|
107
|
+
6.306905285930553,
|
|
108
|
+
-3.5129766590736944,
|
|
109
|
+
15.252822127138128
|
|
110
|
+
],
|
|
111
|
+
[
|
|
112
|
+
0.547075800822676,
|
|
113
|
+
-3.6955217561401175,
|
|
114
|
+
-4.050262727400925,
|
|
115
|
+
7.126316380731758,
|
|
116
|
+
-3.9436029075366887,
|
|
117
|
+
-8.311080144723912,
|
|
118
|
+
-3.61618331933882,
|
|
119
|
+
1.4320057463930302,
|
|
120
|
+
-7.559786226876071
|
|
121
|
+
],
|
|
122
|
+
[
|
|
123
|
+
-10.39133151690414,
|
|
124
|
+
-0.2518686768869586,
|
|
125
|
+
8.945916841206746,
|
|
126
|
+
-3.943602907536662,
|
|
127
|
+
15.317938990949708,
|
|
128
|
+
13.263892201101733,
|
|
129
|
+
-2.6929898725558457,
|
|
130
|
+
3.6058168109879043,
|
|
131
|
+
12.624949118394657
|
|
132
|
+
],
|
|
133
|
+
[
|
|
134
|
+
-38.71154103482345,
|
|
135
|
+
37.857140738093214,
|
|
136
|
+
1.6797355231155888,
|
|
137
|
+
-8.311080144723876,
|
|
138
|
+
13.263892201101948,
|
|
139
|
+
124.24805779986187,
|
|
140
|
+
22.899513669495047,
|
|
141
|
+
-14.324932640812293,
|
|
142
|
+
36.16340587060271
|
|
143
|
+
],
|
|
144
|
+
[
|
|
145
|
+
-33.46098938144807,
|
|
146
|
+
32.560538851614695,
|
|
147
|
+
6.306905285930513,
|
|
148
|
+
-3.6161833193387856,
|
|
149
|
+
-2.6929898725557226,
|
|
150
|
+
22.899513669495008,
|
|
151
|
+
51.09820040428655,
|
|
152
|
+
-8.294874911773647,
|
|
153
|
+
48.405210531734745
|
|
154
|
+
],
|
|
155
|
+
[
|
|
156
|
+
-0.3955310086525263,
|
|
157
|
+
-8.208930061400002,
|
|
158
|
+
-3.5129766590736877,
|
|
159
|
+
1.4320057463930254,
|
|
160
|
+
3.605816810987901,
|
|
161
|
+
-14.324932640812401,
|
|
162
|
+
-8.294874911773736,
|
|
163
|
+
49.582000494612195,
|
|
164
|
+
-4.6890581007868
|
|
165
|
+
],
|
|
166
|
+
[
|
|
167
|
+
-43.85232089835642,
|
|
168
|
+
32.3086701747314,
|
|
169
|
+
15.252822127138115,
|
|
170
|
+
-7.559786226876052,
|
|
171
|
+
12.624949118394866,
|
|
172
|
+
36.16340587060295,
|
|
173
|
+
48.40521053173492,
|
|
174
|
+
-4.689058100786744,
|
|
175
|
+
61.03015965013451
|
|
176
|
+
]
|
|
177
|
+
],
|
|
178
|
+
"training_ceiling_s": 1.32117030251898,
|
|
179
|
+
"training_hull_distance": 6.613238330602411,
|
|
180
|
+
"validated_distance_max": 11.433498320068239,
|
|
181
|
+
"validated_machine": "PBXM"
|
|
182
|
+
}
|