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 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
+ }