FusionDB reactor browser¶
Lists all reactors side by side (one column per reactor) with every available field, including derived values. Columns with warnings are highlighted in yellow; mismatches beyond the 1% tolerance are highlighted in red (red overrides yellow). Warning messages are echoed below the table.
In [1]:
Copied!
from pathlib import Path
import numbers
import time
import warnings
import cProfile
import pstats
import sys
def find_repo_root(start: Path) -> Path:
for path in (start, *start.parents):
if (path / 'reactors').is_dir() and (path / 'src' / 'fusdb').is_dir():
return path
return start
root = find_repo_root(Path.cwd())
src_path = str(root / 'src')
if src_path not in sys.path:
sys.path.insert(0, src_path)
import pandas as pd # Pandas is required for styling
from IPython.display import display
# Import current API
from fusdb import Reactor, make_variable
from fusdb.utils import as_profile_array
from pathlib import Path
import numbers
import time
import warnings
import cProfile
import pstats
import sys
def find_repo_root(start: Path) -> Path:
for path in (start, *start.parents):
if (path / 'reactors').is_dir() and (path / 'src' / 'fusdb').is_dir():
return path
return start
root = find_repo_root(Path.cwd())
src_path = str(root / 'src')
if src_path not in sys.path:
sys.path.insert(0, src_path)
import pandas as pd # Pandas is required for styling
from IPython.display import display
# Import current API
from fusdb import Reactor, make_variable
from fusdb.utils import as_profile_array
In [2]:
Copied!
pd.set_option("display.max_rows", None)
pd.set_option("display.max_columns", None)
pd.set_option("display.width", None)
PROFILE_TOP = 25
profiler = cProfile.Profile()
profiler.enable()
warnings.filterwarnings("ignore", message="Unknown solver tag 'verbosity'.*")
# Load reactors from YAML format
reactors = {}
computed_flags: dict[tuple[str, str], bool] = {}
input_status: dict[tuple[str, str], str] = {}
warning_messages: dict[str, list[str]] = {}
explicit_params_by_reactor: dict[str, set[str]] = {}
variable_status: dict[tuple[str, str], object] = {}
variable_rank: dict[tuple[str, str], int | None] = {}
relation_status: dict[tuple[str, str], object] = {}
LOG_STAGE_TIMES = True
# Metadata fields
metadata_fields = ['id', 'name', 'organization', 'country', 'year', 'doi', 'notes']
def log(msg: str) -> None:
print(msg)
def numeric_value(value: object) -> float | None:
"""Extract a comparable scalar from value/profile types."""
profile = as_profile_array(value)
if profile is not None:
return float(profile.mean())
try:
import numpy as np
except Exception:
np = None
if np is not None:
if isinstance(value, np.ndarray):
if value.size == 1:
return float(value.item())
return None
if isinstance(value, np.generic):
try:
return float(value.item())
except Exception:
return None
if isinstance(value, numbers.Real) and not isinstance(value, bool):
return float(value)
try:
import sympy as sp
except Exception:
sp = None
if sp is not None and isinstance(value, sp.Expr) and value.is_number:
try:
return float(value.evalf())
except Exception:
return None
try:
return float(value)
except Exception:
return None
def to_display_value(value: object) -> object:
"""Convert value to a compact display string or scalar."""
profile = as_profile_array(value)
if profile is not None:
if profile.size == 0:
return "profile(n=0)"
mean_val = float(profile.mean())
min_val = float(profile.min())
max_val = float(profile.max())
mean_text = f"{mean_val:.3g}".replace("e+0", "e").replace("e+", "e")
min_text = f"{min_val:.3g}".replace("e+0", "e").replace("e+", "e")
max_text = f"{max_val:.3g}".replace("e+0", "e").replace("e+", "e")
return f"profile(n={profile.size}, mean={mean_text}, min={min_text}, max={max_text})"
try:
import numpy as np
except Exception:
np = None
if np is not None and isinstance(value, np.ndarray):
if value.size == 1:
return float(value.item())
return f"array({value.size})"
try:
import sympy as sp
except Exception:
sp = None
if sp is not None and isinstance(value, sp.Expr) and value.is_number:
try:
return float(value.evalf())
except Exception:
return value
return value
def format_sci(value: object) -> str:
"""Format value for scientific notation if needed."""
if isinstance(value, str):
return value
if isinstance(value, numbers.Real) and not isinstance(value, bool):
if value != 0 and (abs(value) >= 1e4 or abs(value) <= 1e-4):
return f"{value:.2e}"
if value is None:
return ""
return str(value)
def exact_match(base: object, other: object, eps: float = 1e-12) -> bool:
"""Check if two values match (arrays compared element-wise)."""
base_prof = as_profile_array(base)
other_prof = as_profile_array(other)
if base_prof is not None or other_prof is not None:
if base_prof is None or other_prof is None:
return False
if base_prof.shape != other_prof.shape:
return False
import numpy as np
return bool(np.allclose(base_prof, other_prof, rtol=eps, atol=eps))
base_num = numeric_value(base)
other_num = numeric_value(other)
if base_num is None or other_num is None:
return False
scale = max(abs(base_num), abs(other_num), 1.0)
return abs(base_num - other_num) <= eps * scale
def classify_input_status(input_val: object, solved_val: object, tol: float = 0.01) -> str:
"""Classify consistency between input and solved values."""
input_num = numeric_value(input_val)
solved_num = numeric_value(solved_val)
if input_num is None or solved_num is None:
return "none"
delta = solved_num - input_num
if delta == 0:
return "green"
scale = max(abs(input_num), abs(solved_num), 1.0)
if abs(delta) <= tol * scale:
return "yellow"
return "red"
log(f"Loading reactors from {root.resolve()}")
# Find all reactor directories
reactor_dirs = []
reactors_path = root / 'reactors'
if reactors_path.is_dir():
for item in reactors_path.iterdir():
if item.is_dir():
yaml_path = item / 'reactor.yaml'
if yaml_path.exists():
reactor_dirs.append((item, yaml_path))
for reactor_dir, yaml_path in reactor_dirs:
log(f"Loading {reactor_dir.name} from {yaml_path.name}...")
t0 = time.perf_counter()
try:
reactor = Reactor.from_yaml(yaml_path)
t_loaded = time.perf_counter()
try:
reactor.solve()
except Exception as e:
log(f" ERROR solving reactor: {e}")
t_solved = time.perf_counter()
except Exception as e:
log(f" ERROR loading reactor: {e}")
import traceback
traceback.print_exc()
continue
load_elapsed = time.perf_counter() - t0
log(f" Loaded reactor in {load_elapsed:.2f}s")
if LOG_STAGE_TIMES:
log(f" - parse/load: {t_loaded - t0:.2f}s")
log(f" - solve: {t_solved - t_loaded:.2f}s")
# Get reactor ID from reactor properties
reactor_id = reactor.id or reactor_dir.name
reactors[reactor_id] = reactor
# Determine which variables were explicitly provided
explicit_params = {
name for name, var in reactor.variables_dict.items()
if var.input_source == "explicit"
}
explicit_params_by_reactor[reactor_id] = explicit_params
# Track which variables were computed vs explicit
for name, var in reactor.variables_dict.items():
input_val = var.input_value
current_val = var.current_value if var.current_value is not None else input_val
computed_present = (
var.current_value is not None
and (var.input_source is None or not exact_match(var.input_value, var.current_value))
)
computed_flags[(name, reactor_id)] = computed_present
input_status[(name, reactor_id)] = classify_input_status(input_val, current_val)
# Capture variable validity status from solver diagnostics
t_diag = time.perf_counter()
try:
diag = reactor.diagnose()
for name, status, rank in diag["variable_issues"]:
variable_status[(name, reactor_id)] = status
variable_rank[(name, reactor_id)] = rank
for rel_name, status, _residual in diag["violated_relations"]:
relation_status[(reactor_id, rel_name)] = status
except Exception as e:
warning_messages.setdefault(reactor_id, []).append(f"diagnose failed: {e}")
t_diag_done = time.perf_counter()
if LOG_STAGE_TIMES:
log(f" - diagnostics: {t_diag_done - t_diag:.2f}s")
warning_messages[reactor_id] = warning_messages.get(reactor_id, [])
profiler.disable()
pstats.Stats(profiler).sort_stats("cumtime").print_stats(PROFILE_TOP)
pd.set_option("display.max_rows", None)
pd.set_option("display.max_columns", None)
pd.set_option("display.width", None)
PROFILE_TOP = 25
profiler = cProfile.Profile()
profiler.enable()
warnings.filterwarnings("ignore", message="Unknown solver tag 'verbosity'.*")
# Load reactors from YAML format
reactors = {}
computed_flags: dict[tuple[str, str], bool] = {}
input_status: dict[tuple[str, str], str] = {}
warning_messages: dict[str, list[str]] = {}
explicit_params_by_reactor: dict[str, set[str]] = {}
variable_status: dict[tuple[str, str], object] = {}
variable_rank: dict[tuple[str, str], int | None] = {}
relation_status: dict[tuple[str, str], object] = {}
LOG_STAGE_TIMES = True
# Metadata fields
metadata_fields = ['id', 'name', 'organization', 'country', 'year', 'doi', 'notes']
def log(msg: str) -> None:
print(msg)
def numeric_value(value: object) -> float | None:
"""Extract a comparable scalar from value/profile types."""
profile = as_profile_array(value)
if profile is not None:
return float(profile.mean())
try:
import numpy as np
except Exception:
np = None
if np is not None:
if isinstance(value, np.ndarray):
if value.size == 1:
return float(value.item())
return None
if isinstance(value, np.generic):
try:
return float(value.item())
except Exception:
return None
if isinstance(value, numbers.Real) and not isinstance(value, bool):
return float(value)
try:
import sympy as sp
except Exception:
sp = None
if sp is not None and isinstance(value, sp.Expr) and value.is_number:
try:
return float(value.evalf())
except Exception:
return None
try:
return float(value)
except Exception:
return None
def to_display_value(value: object) -> object:
"""Convert value to a compact display string or scalar."""
profile = as_profile_array(value)
if profile is not None:
if profile.size == 0:
return "profile(n=0)"
mean_val = float(profile.mean())
min_val = float(profile.min())
max_val = float(profile.max())
mean_text = f"{mean_val:.3g}".replace("e+0", "e").replace("e+", "e")
min_text = f"{min_val:.3g}".replace("e+0", "e").replace("e+", "e")
max_text = f"{max_val:.3g}".replace("e+0", "e").replace("e+", "e")
return f"profile(n={profile.size}, mean={mean_text}, min={min_text}, max={max_text})"
try:
import numpy as np
except Exception:
np = None
if np is not None and isinstance(value, np.ndarray):
if value.size == 1:
return float(value.item())
return f"array({value.size})"
try:
import sympy as sp
except Exception:
sp = None
if sp is not None and isinstance(value, sp.Expr) and value.is_number:
try:
return float(value.evalf())
except Exception:
return value
return value
def format_sci(value: object) -> str:
"""Format value for scientific notation if needed."""
if isinstance(value, str):
return value
if isinstance(value, numbers.Real) and not isinstance(value, bool):
if value != 0 and (abs(value) >= 1e4 or abs(value) <= 1e-4):
return f"{value:.2e}"
if value is None:
return ""
return str(value)
def exact_match(base: object, other: object, eps: float = 1e-12) -> bool:
"""Check if two values match (arrays compared element-wise)."""
base_prof = as_profile_array(base)
other_prof = as_profile_array(other)
if base_prof is not None or other_prof is not None:
if base_prof is None or other_prof is None:
return False
if base_prof.shape != other_prof.shape:
return False
import numpy as np
return bool(np.allclose(base_prof, other_prof, rtol=eps, atol=eps))
base_num = numeric_value(base)
other_num = numeric_value(other)
if base_num is None or other_num is None:
return False
scale = max(abs(base_num), abs(other_num), 1.0)
return abs(base_num - other_num) <= eps * scale
def classify_input_status(input_val: object, solved_val: object, tol: float = 0.01) -> str:
"""Classify consistency between input and solved values."""
input_num = numeric_value(input_val)
solved_num = numeric_value(solved_val)
if input_num is None or solved_num is None:
return "none"
delta = solved_num - input_num
if delta == 0:
return "green"
scale = max(abs(input_num), abs(solved_num), 1.0)
if abs(delta) <= tol * scale:
return "yellow"
return "red"
log(f"Loading reactors from {root.resolve()}")
# Find all reactor directories
reactor_dirs = []
reactors_path = root / 'reactors'
if reactors_path.is_dir():
for item in reactors_path.iterdir():
if item.is_dir():
yaml_path = item / 'reactor.yaml'
if yaml_path.exists():
reactor_dirs.append((item, yaml_path))
for reactor_dir, yaml_path in reactor_dirs:
log(f"Loading {reactor_dir.name} from {yaml_path.name}...")
t0 = time.perf_counter()
try:
reactor = Reactor.from_yaml(yaml_path)
t_loaded = time.perf_counter()
try:
reactor.solve()
except Exception as e:
log(f" ERROR solving reactor: {e}")
t_solved = time.perf_counter()
except Exception as e:
log(f" ERROR loading reactor: {e}")
import traceback
traceback.print_exc()
continue
load_elapsed = time.perf_counter() - t0
log(f" Loaded reactor in {load_elapsed:.2f}s")
if LOG_STAGE_TIMES:
log(f" - parse/load: {t_loaded - t0:.2f}s")
log(f" - solve: {t_solved - t_loaded:.2f}s")
# Get reactor ID from reactor properties
reactor_id = reactor.id or reactor_dir.name
reactors[reactor_id] = reactor
# Determine which variables were explicitly provided
explicit_params = {
name for name, var in reactor.variables_dict.items()
if var.input_source == "explicit"
}
explicit_params_by_reactor[reactor_id] = explicit_params
# Track which variables were computed vs explicit
for name, var in reactor.variables_dict.items():
input_val = var.input_value
current_val = var.current_value if var.current_value is not None else input_val
computed_present = (
var.current_value is not None
and (var.input_source is None or not exact_match(var.input_value, var.current_value))
)
computed_flags[(name, reactor_id)] = computed_present
input_status[(name, reactor_id)] = classify_input_status(input_val, current_val)
# Capture variable validity status from solver diagnostics
t_diag = time.perf_counter()
try:
diag = reactor.diagnose()
for name, status, rank in diag["variable_issues"]:
variable_status[(name, reactor_id)] = status
variable_rank[(name, reactor_id)] = rank
for rel_name, status, _residual in diag["violated_relations"]:
relation_status[(reactor_id, rel_name)] = status
except Exception as e:
warning_messages.setdefault(reactor_id, []).append(f"diagnose failed: {e}")
t_diag_done = time.perf_counter()
if LOG_STAGE_TIMES:
log(f" - diagnostics: {t_diag_done - t_diag:.2f}s")
warning_messages[reactor_id] = warning_messages.get(reactor_id, [])
profiler.disable()
pstats.Stats(profiler).sort_stats("cumtime").print_stats(PROFILE_TOP)
Loading reactors from /home/alessmor/Scrivania/fusdb Loading HAMMIR from reactor.yaml...
_execute_pass: breaking after 101 iterations _execute_pass: breaking after 101 iterations _execute_pass: breaking after 101 iterations Inconsistency: overriding V_p by 10.2 -> 712214161449.595 (global) _execute_pass: breaking after 101 iterations
Loaded reactor in 4.25s - parse/load: 0.02s - solve: 4.23s - diagnostics: 0.19s Loading STEP_2024 from reactor.yaml...
_execute_pass: breaking after 101 iterations Volume consistency warning: integral(dV)=0.0800382 differs from V_p=788.946 (rel_delta=99.990%, tol=1%). Inconsistency: relation 'Total fusion power' computed P_fus = 7e-17, but input specifies P_fus = 1.56e+09 _execute_pass: breaking after 101 iterations _execute_pass: breaking after 101 iterations Inconsistency: overriding P_fus by 1 -> 0.0 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding I_p by 4 -> 2282.8254449777305 (global) _execute_pass: breaking after 101 iterations Volume consistency warning: integral(dV)=8.09452e-10 differs from V_p=788.946 (rel_delta=100.000%, tol=1%). Inconsistency: overriding I_p by 10.4 -> 1.0086978363688104e-07 (global) _execute_pass: breaking after 101 iterations Volume consistency warning: integral(dV)=1.5804e-30 differs from V_p=788.946 (rel_delta=100.000%, tol=1%). Soft constraint violated (variable f_GW): f_GW <= 1 Soft constraint violated (relation Troyon margin): troyon_margin <= 0 Soft constraint violated (relation Greenwald margin): greenwald_margin <= 0
Loaded reactor in 10.15s - parse/load: 1.08s - solve: 9.07s - diagnostics: 0.24s Loading DEMO_2022 from reactor.yaml...
Inconsistency: relation 'L-H transition threshold power' computed P_LH = 9.3e-07, but input specifies P_LH = 1.21e+08 Inconsistency: relation 'Greenwald density fraction' computed f_GW = 1.49e-20, but input specifies f_GW = 1.2 Inconsistency: relation 'Total fusion power' computed P_fus = 3.46e-31, but input specifies P_fus = 2e+09 _execute_pass: breaking after 101 iterations Volume consistency warning: integral(dV)=2465.2 differs from V_p=2568.18 (rel_delta=4.010%, tol=1%). Inconsistency: overriding P_LH by 14.1 -> 9.296158442131741e-07 _execute_pass: breaking after 101 iterations Inconsistency: overriding f_GW by 19.9 -> 1.4884954488276148e-20 _execute_pass: breaking after 101 iterations Inconsistency: overriding H98_y2 by 0.98 -> -1.5701151490077336e-10 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 0 -> 2.5 _execute_pass: breaking after 101 iterations Inconsistency: overriding afuel by 1.23 -> -10.860804248408936 (global) _execute_pass: breaking after 101 iterations
Loaded reactor in 104.89s - parse/load: 1.68s - solve: 103.21s - diagnostics: 0.21s Loading INFINITY_TWO from reactor.yaml...
_execute_pass: breaking after 101 iterations _execute_pass: breaking after 101 iterations _execute_pass: breaking after 101 iterations
Loaded reactor in 1.87s - parse/load: 0.29s - solve: 1.59s - diagnostics: 0.23s Loading STELLARIS from reactor.yaml...
_execute_pass: breaking after 101 iterations Inconsistency: relation 'Total fusion power' computed P_fus = 2.98e+07, but input specifies P_fus = 2.7e+09 Inconsistency: relation 'Energy confinement time' computed tau_E = 15.5, but input specifies tau_E = 1.46 _execute_pass: breaking after 101 iterations Inconsistency: relation 'Normalized beta' computed beta_N = 0, but input specifies beta_N = 0.0276 _execute_pass: breaking after 101 iterations Inconsistency: overriding tau_E by 1 -> 0.0 _execute_pass: breaking after 101 iterations Inconsistency: overriding beta_N by 0.0276 -> 0.0 _execute_pass: breaking after 101 iterations Inconsistency: overriding P_fus by 1 -> 0.0 (global) _execute_pass: breaking after 101 iterations
Loaded reactor in 13.03s - parse/load: 0.30s - solve: 12.73s - diagnostics: 0.19s Loading ARC_2015 from reactor.yaml...
Inconsistency: relation 'Normalized beta' computed beta_N = 2.53, but input specifies beta_N = 2.59 Inconsistency: relation 'Total fusion power' computed P_fus = 4.33e+08, but input specifies P_fus = 5.25e+08 Inconsistency: relation 'Energy confinement time' computed tau_E = 0.655, but input specifies tau_E = 0.64 _execute_pass: breaking after 101 iterations Volume consistency warning: integral(dV)=136.647 differs from V_p=153 (rel_delta=10.688%, tol=1%). Inconsistency: overriding tau_E by 0.24 -> 1.1118535173165673 _execute_pass: breaking after 101 iterations Inconsistency: overriding beta_N by 0.00977 -> 2.5323589743589743 _execute_pass: breaking after 101 iterations Inconsistency: overriding P_fus by 0.677 -> 110476209.83835113 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding B0 by 0.0261 -> 9.769692254660859 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding beta_N by 0.0163 -> 2.689170419327803 _execute_pass: breaking after 101 iterations Inconsistency: overriding I_p by 0.0261 -> 8282999.955061359 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding beta_N by 0.00977 -> 2.5323589743520016 _execute_pass: breaking after 101 iterations Inconsistency: overriding I_p by 0.027 -> 7783506.801811429 (global) _execute_pass: breaking after 101 iterations Inconsistency: overriding beta_N by 0.0172 -> 2.694868753230266 _execute_pass: breaking after 101 iterations
Loaded reactor in 62.71s
- parse/load: 0.42s
- solve: 62.29s
- diagnostics: 0.27s
390874064 function calls (386048555 primitive calls) in 198.042 seconds
Ordered by: cumulative time
List reduced from 6401 to 25 due to restriction <25>
ncalls tottime percall cumtime percall filename:lineno(function)
23 0.000 0.000 198.258 8.620 /home/alessmor/Scrivania/fusdb/.venv/lib/python3.10/site-packages/IPython/core/interactiveshell.py:3543(run_code)
5137/23 0.075 0.000 198.258 8.620 {built-in method builtins.exec}
1 0.005 0.005 198.257 198.257 /tmp/ipykernel_116809/1570770633.py:1(<module>)
6 0.003 0.001 193.121 32.187 /home/alessmor/Scrivania/fusdb/src/fusdb/reactor_class.py:262(solve)
6 0.000 0.000 193.116 32.186 /home/alessmor/Scrivania/fusdb/src/fusdb/reactor_class.py:185(_solve_once)
6 1.467 0.245 192.093 32.015 /home/alessmor/Scrivania/fusdb/src/fusdb/relationsystem_class.py:1654(solve)
118968 1.107 0.000 89.978 0.001 /home/alessmor/Scrivania/fusdb/src/fusdb/relationsystem_class.py:574(_accept_candidate_values)
406249 13.138 0.000 74.788 0.000 /home/alessmor/Scrivania/fusdb/src/fusdb/relationsystem_class.py:386(_to_scalar_values)
216757 0.333 0.000 66.828 0.000 /home/alessmor/Scrivania/fusdb/src/fusdb/relation_util.py:363(apply_relation)
216757 0.547 0.000 65.479 0.000 /home/alessmor/Scrivania/fusdb/src/fusdb/relation_util.py:358(_call_relation_forward)
300406 0.701 0.000 58.919 0.000 /home/alessmor/Scrivania/fusdb/src/fusdb/relationsystem_class.py:814(_constraints_violated)
214411 0.106 0.000 46.601 0.000 /home/alessmor/Scrivania/fusdb/src/fusdb/relation_class.py:96(evaluate)
214411 0.302 0.000 46.495 0.000 /home/alessmor/Scrivania/fusdb/src/fusdb/relation_util.py:400(evaluate_relation)
106310 1.063 0.000 33.237 0.000 /home/alessmor/Scrivania/fusdb/src/fusdb/relationsystem_class.py:712(_set_value)
6843057 4.421 0.000 32.497 0.000 /home/alessmor/Scrivania/fusdb/.venv/lib/python3.10/site-packages/numpy/_core/fromnumeric.py:3735(mean)
81862 0.114 0.000 31.237 0.000 /home/alessmor/Scrivania/fusdb/src/fusdb/relation_class.py:53(__call__)
54228 17.360 0.000 30.610 0.001 /home/alessmor/Scrivania/fusdb/src/fusdb/relations/reactivities/tabulated_reactivities.py:304(reactivity_from_xsection_table)
68846 0.144 0.000 29.972 0.000 /home/alessmor/Scrivania/fusdb/.venv/lib/python3.10/site-packages/numpy/_core/arrayprint.py:1685(_array_str_implementation)
68846 0.241 0.000 29.810 0.000 /home/alessmor/Scrivania/fusdb/.venv/lib/python3.10/site-packages/numpy/_core/arrayprint.py:605(array2string)
68846 0.190 0.000 29.068 0.000 /home/alessmor/Scrivania/fusdb/.venv/lib/python3.10/site-packages/numpy/_core/arrayprint.py:550(wrapper)
65455 0.262 0.000 28.974 0.000 /home/alessmor/Scrivania/fusdb/src/fusdb/relationsystem_class.py:2545(_relation_status)
68846 0.308 0.000 28.798 0.000 /home/alessmor/Scrivania/fusdb/.venv/lib/python3.10/site-packages/numpy/_core/arrayprint.py:567(_array2string)
1639 0.003 0.000 28.771 0.018 /home/alessmor/Scrivania/fusdb/src/fusdb/relationsystem_class.py:2540(_violated_relations)
1639 0.076 0.000 28.768 0.018 /home/alessmor/Scrivania/fusdb/src/fusdb/relationsystem_class.py:2543(<setcomp>)
6843167 11.637 0.000 28.076 0.000 /home/alessmor/Scrivania/fusdb/.venv/lib/python3.10/site-packages/numpy/_core/_methods.py:118(_mean)
Out[2]:
<pstats.Stats at 0x7f230c2ac430>
In [3]:
Copied!
# Build table data
parameter_names = sorted(
{name for reactor in reactors.values() for name in reactor.variables_dict.keys()},
key=lambda name: (name.lower(), name),
)
field_names = metadata_fields + parameter_names
log(f"Building table: {len(metadata_fields)} metadata fields, {len(parameter_names)} parameters")
parameter_set = set(parameter_names)
def within_tol(base: object, other: object, rel_tol: float | None, abs_tol: float | None, default_rel: float = 0.01) -> bool:
"""Check if two values are within tolerance."""
base_num = numeric_value(base)
other_num = numeric_value(other)
if base_num is None or other_num is None:
return False
if rel_tol is None and abs_tol is None:
rel = default_rel
abs_t = 0.0
else:
rel = rel_tol or 0.0
abs_t = abs_tol or 0.0
scale = max(abs(base_num), abs(other_num), 1.0)
tol = max(abs_t, rel * scale)
return abs(base_num - other_num) <= tol
table_data: dict[str, dict[str, object]] = {}
for rid, reactor in sorted(reactors.items()):
column_data: dict[str, object] = {}
for name in field_names:
if name in parameter_set:
var = reactor.variables_dict.get(name)
if var is None:
column_data[name] = ""
continue
input_val = var.input_value
current_val = var.current_value if var.current_value is not None else input_val
if input_val is None and current_val is None:
cell_text = ""
elif input_val is None:
cell_text = format_sci(to_display_value(current_val))
elif current_val is None or exact_match(input_val, current_val):
cell_text = format_sci(to_display_value(input_val))
else:
base_str = format_sci(to_display_value(input_val))
current_str = format_sci(to_display_value(current_val))
cell_text = f"{base_str} -> {current_str}"
column_data[name] = cell_text
else:
column_data[name] = format_sci(getattr(reactor, name, None))
table_data[rid] = column_data
df = pd.DataFrame.from_dict(table_data, orient="columns")
df = df.reindex(index=field_names, columns=sorted(reactors.keys()))
if len(df.columns) != len(reactors):
log(f"WARNING: expected {len(reactors)} reactor columns, got {len(df.columns)}")
def style_cells(data: pd.DataFrame) -> pd.DataFrame:
"""Style cells based on input/computed status and value consistency."""
styles = pd.DataFrame('', index=data.index, columns=data.columns)
for field in styles.index:
for rid in styles.columns:
parts = ['white-space: pre-line;']
var = reactors[rid].variables_dict.get(field)
if var is None:
styles.loc[field, rid] = ' '.join(parts)
continue
input_val = var.input_value
current_val = var.current_value if var.current_value is not None else input_val
if var.input_source is not None and input_val is not None:
if current_val is None or exact_match(input_val, current_val):
parts.append('color: #0a7d2a;')
elif within_tol(input_val, current_val, var.rel_tol, var.abs_tol):
parts.append('color: #b58900;')
else:
parts.append('color: #b30000; font-weight: 600;')
if computed_flags.get((field, rid), False) and current_val is not None:
parts.append('text-decoration: underline;')
styles.loc[field, rid] = ' '.join(parts)
return styles
styled = df.style.apply(style_cells, axis=None)
styled = styled.format(escape=None)
display(styled)
# Show warnings below the table
for rid, msgs in warning_messages.items():
if not msgs:
continue
print(f'Warnings for {rid}:')
for msg in msgs:
print(f' - {msg}')
# Build table data
parameter_names = sorted(
{name for reactor in reactors.values() for name in reactor.variables_dict.keys()},
key=lambda name: (name.lower(), name),
)
field_names = metadata_fields + parameter_names
log(f"Building table: {len(metadata_fields)} metadata fields, {len(parameter_names)} parameters")
parameter_set = set(parameter_names)
def within_tol(base: object, other: object, rel_tol: float | None, abs_tol: float | None, default_rel: float = 0.01) -> bool:
"""Check if two values are within tolerance."""
base_num = numeric_value(base)
other_num = numeric_value(other)
if base_num is None or other_num is None:
return False
if rel_tol is None and abs_tol is None:
rel = default_rel
abs_t = 0.0
else:
rel = rel_tol or 0.0
abs_t = abs_tol or 0.0
scale = max(abs(base_num), abs(other_num), 1.0)
tol = max(abs_t, rel * scale)
return abs(base_num - other_num) <= tol
table_data: dict[str, dict[str, object]] = {}
for rid, reactor in sorted(reactors.items()):
column_data: dict[str, object] = {}
for name in field_names:
if name in parameter_set:
var = reactor.variables_dict.get(name)
if var is None:
column_data[name] = ""
continue
input_val = var.input_value
current_val = var.current_value if var.current_value is not None else input_val
if input_val is None and current_val is None:
cell_text = ""
elif input_val is None:
cell_text = format_sci(to_display_value(current_val))
elif current_val is None or exact_match(input_val, current_val):
cell_text = format_sci(to_display_value(input_val))
else:
base_str = format_sci(to_display_value(input_val))
current_str = format_sci(to_display_value(current_val))
cell_text = f"{base_str} -> {current_str}"
column_data[name] = cell_text
else:
column_data[name] = format_sci(getattr(reactor, name, None))
table_data[rid] = column_data
df = pd.DataFrame.from_dict(table_data, orient="columns")
df = df.reindex(index=field_names, columns=sorted(reactors.keys()))
if len(df.columns) != len(reactors):
log(f"WARNING: expected {len(reactors)} reactor columns, got {len(df.columns)}")
def style_cells(data: pd.DataFrame) -> pd.DataFrame:
"""Style cells based on input/computed status and value consistency."""
styles = pd.DataFrame('', index=data.index, columns=data.columns)
for field in styles.index:
for rid in styles.columns:
parts = ['white-space: pre-line;']
var = reactors[rid].variables_dict.get(field)
if var is None:
styles.loc[field, rid] = ' '.join(parts)
continue
input_val = var.input_value
current_val = var.current_value if var.current_value is not None else input_val
if var.input_source is not None and input_val is not None:
if current_val is None or exact_match(input_val, current_val):
parts.append('color: #0a7d2a;')
elif within_tol(input_val, current_val, var.rel_tol, var.abs_tol):
parts.append('color: #b58900;')
else:
parts.append('color: #b30000; font-weight: 600;')
if computed_flags.get((field, rid), False) and current_val is not None:
parts.append('text-decoration: underline;')
styles.loc[field, rid] = ' '.join(parts)
return styles
styled = df.style.apply(style_cells, axis=None)
styled = styled.format(escape=None)
display(styled)
# Show warnings below the table
for rid, msgs in warning_messages.items():
if not msgs:
continue
print(f'Warnings for {rid}:')
for msg in msgs:
print(f' - {msg}')
Building table: 7 metadata fields, 114 parameters
| ARC_2015 | DEMO_2022 | Hammir2024 | InfinityTwo2025 | STEP2024EBCC | Stellaris2025 | |
|---|---|---|---|---|---|---|
| id | ARC_2015 | DEMO_2022 | Hammir2024 | InfinityTwo2025 | STEP2024EBCC | Stellaris2025 |
| name | ARC 2015 | EU-DEMO 2022 | Hammir 2024 | INFINITY TWO 2025 | STEP 2024 EB-CC | Stellaris 2025 - Point A |
| organization | MIT | EUROFUSION | Realta | TypeOne Energy | UKAEA | Proxima |
| country | United States | EUU | United Kingdom | |||
| year | 2015 | 2022 | 2024 | 2025 | 2024 | 2025 |
| doi | 10.1016/j.fusengdes.2015.07.008 | 10.1016/j.fusengdes.2022.113080 | 10.1088/1741-4326/ad3fcb | 10.1017/S0022377825000364 | 10.1088/1741-4326/ad6ea2 | 10.1016/j.fusengdes.2025.114868 |
| notes | Conceptual compact tokamak from MIT | EU-DEMO Physics Baseline 2022 | Refers to EC+EBW heating scenario with Conservative Confinement conditions | |||
| _fraction | 0.0 | 0.0 | 0.0 | 0.0 | ||
| A | 2.92 | 3.1 | 10.0 | 1.8 | 9.8 | |
| a | 1.13 | 2.9 | 1.25 | 8.91e-17 | 1.3 | |
| A_p | 232.37218811842646 | 1554.4050448854432 | 600.3569688145529 | 327.0 | ||
| afuel | 3.235458116807753 | 2.5 -> -10.860804248408936 | 3.0 | 3.0 | 3.0 | 3.0943428057670146 |
| B0 | 9.2 -> 9.769692254660859 | 5.86 | 3.2 | 9.0 | ||
| B_max | 23.0 | 12.0 | ||||
| beta_limit | 0.021008080030781074 | 0.02924561609979993 | 9.99e-06 | |||
| beta_N | 2.59 -> 2.694868753230266 | 2.5 | 0.0393 | 0.0276 -> 0.0 | ||
| beta_T | 0.019 | 0.026112157231964223 | 0.139 | 0.0 | ||
| delta | 0.003397448710900506 | 0.495 | 0.39998399634202103 | |||
| delta_95 | 0.002264965807267005 | 0.33 | 0.26665599756134745 | |||
| eps | 0.3424242424242424 | 0.3222222222222222 | 0.1 | 0.5555555555555556 | 0.10204081632653061 | |
| f_BS | 0.63 | |||||
| f_CD | 0.05 | |||||
| f_D | 0.24563451458294777 | 0.5 | 1.33e-23 | 5.32e-24 | 6.10e-24 | 3.15e-24 |
| f_GW | 0.67 | 1.2 -> 1.49e-20 | 2.04e+14 | |||
| f_He3 | 0.0012508489085154852 | 0.0 | 1.0 | 1.0 | 1.0 | 3.15e-24 |
| f_He4 | 0.503284908439265 | 0.0 | 0.0 | 0.0 | 0.0 | 0.12505745878861985 |
| f_NI | 0.39 | |||||
| f_T | 0.24982972806927184 | 0.5 | 1.33e-23 | 5.32e-24 | 6.10e-24 | 0.8749425412113799 |
| G89 | 0.14 | |||||
| greenwald_margin | -6.44e+19 | -6.72e+19 | 1.64e+20 | |||
| H89 | 2.8 | |||||
| H98_y2 | 1.8 | 0.98 -> -1.57e-10 | 1.03 | |||
| I_p | 7.80e+06 -> 7.78e+06 | 1.78e+07 | 2.27e+07 -> 1.01e-07 | 1.60e+09 | ||
| kappa | 1.84 | 1.848 | 3.0 | |||
| kappa_95 | 1.6428571428571432 | 1.65 | 2.8 | |||
| kappa_ipb | 1.8394621796250965 | 1.7189237795764651 | 2.7755898202811764 | 1.0000070376114356 | ||
| L_p | 11.211198645255081 | 28.96635924948221 | 28.573411049007305 | |||
| li | 0.67 | |||||
| n0 | 1.80e+20 | 5.06e+20 | ||||
| n_avg | 1.30e+20 | 1.0 | 7.50e+19 | 1.88e+20 | 1.64e+20 | 3.17e+20 |
| n_D | profile(n=51, mean=6.5e19, min=6.5e19, max=6.5e19) -> profile(n=51, mean=3.19e19, min=3.19e19, max=3.19e19) | profile(n=51, mean=0.5, min=0.5, max=0.5) | profile(n=51, mean=0, min=0, max=0) | profile(n=51, mean=0, min=0, max=0) | profile(n=51, mean=0, min=0, max=0) | profile(n=51, mean=0, min=0, max=0) |
| n_e | profile(n=51, mean=1.3e20, min=1.3e20, max=1.3e20) | profile(n=51, mean=1, min=1, max=1) | profile(n=51, mean=7.5e19, min=7.5e19, max=7.5e19) | profile(n=51, mean=1.88e20, min=1.88e20, max=1.88e20) | profile(n=51, mean=1.64e20, min=1.64e20, max=1.64e20) | profile(n=51, mean=3.17e20, min=3.17e20, max=3.17e20) |
| n_GW | 1.94e+20 | 6.72e+19 | 8.03e+05 | |||
| n_He3 | profile(n=51, mean=0, min=0, max=0) -> profile(n=51, mean=1.63e17, min=1.63e17, max=1.63e17) | profile(n=51, mean=0, min=0, max=0) | profile(n=51, mean=0, min=0, max=0) | profile(n=51, mean=0, min=0, max=0) | profile(n=51, mean=0, min=0, max=0) | profile(n=51, mean=0, min=0, max=0) |
| n_He4 | profile(n=51, mean=0, min=0, max=0) -> profile(n=51, mean=6.54e19, min=6.54e19, max=6.54e19) | profile(n=51, mean=0, min=0, max=0) | profile(n=51, mean=0, min=0, max=0) | profile(n=51, mean=0, min=0, max=0) | profile(n=51, mean=0, min=0, max=0) | profile(n=51, mean=0, min=0, max=0) |
| n_i | profile(n=51, mean=1.3e20, min=1.3e20, max=1.3e20) | profile(n=51, mean=1, min=1, max=1) | profile(n=51, mean=7.5e19, min=7.5e19, max=7.5e19) | profile(n=51, mean=1.88e20, min=1.88e20, max=1.88e20) | profile(n=51, mean=1.64e20, min=1.64e20, max=1.64e20) | profile(n=51, mean=3.17e20, min=3.17e20, max=3.17e20) |
| n_la | 1.30e+20 | 1.0 | 7.50e+19 | 1.88e+20 | 1.64e+20 | 3.17e+20 |
| n_SUDO | 5.23e+20 | |||||
| n_T | profile(n=51, mean=6.5e19, min=6.5e19, max=6.5e19) -> profile(n=51, mean=3.25e19, min=3.25e19, max=3.25e19) | profile(n=51, mean=0.5, min=0.5, max=0.5) | profile(n=51, mean=0, min=0, max=0) | profile(n=51, mean=0, min=0, max=0) | profile(n=51, mean=0, min=0, max=0) | profile(n=51, mean=0, min=0, max=0) |
| P_aux | 1.26e+08 | 2.00e+08 | 2.00e+07 | 0.0 | 5.00e+07 | |
| P_charged | 2.20e+07 | 6.89e-32 | 2.37e-15 | 7.00e-17 | 1.32e-14 | |
| P_fus | 5.25e+08 -> 1.10e+08 | 2.00e+09 | 2.37e-15 | 8.00e+08 | 1.56e+09 -> 0.0 | 2.70e+09 -> 0.0 |
| P_fus_DD | 1.18e+05 | 3.68e-34 | 1.01e-39 | 2.53e-40 | 2.23e-40 | |
| P_fus_DDn | 5.06e+04 | 1.60e-34 | 3.97e-40 | 1.11e-40 | 9.71e-41 | |
| P_fus_DDn_He3 | 1.25e+04 | 4.01e-35 | 9.96e-41 | 2.78e-41 | 2.43e-41 | |
| P_fus_DDn_n | 3.74e+04 | 1.20e-34 | 2.98e-40 | 8.31e-41 | 7.28e-41 | |
| P_fus_DDp | 6.62e+04 | 2.08e-34 | 6.11e-40 | 1.42e-40 | 1.26e-40 | |
| P_fus_DDp_p | 4.90e+04 | 1.56e-34 | 4.58e-40 | 1.07e-40 | 9.43e-41 | |
| P_fus_DDp_T | 1.64e+04 | 5.21e-35 | 1.53e-40 | 3.56e-41 | 3.16e-41 | |
| P_fus_DHe3 | 2066.853748717398 | 0.0 | 2.37e-15 | 7.00e-17 | 5.88e-40 | |
| P_fus_DHe3_alpha | 406.59432476028167 | 0.0 | 4.66e-16 | 1.38e-17 | 1.16e-40 | |
| P_fus_DHe3_p | 1660.2594239571165 | 0.0 | 1.90e-15 | 5.62e-17 | 4.72e-40 | |
| P_fus_DT | 1.10e+08 | 3.45e-31 | 9.44e-38 | 2.33e-37 | 6.64e-14 | |
| P_fus_DT_alpha | 2.17e+07 | 6.86e-32 | 1.88e-38 | 4.64e-38 | 1.32e-14 | |
| P_fus_DT_n | 8.73e+07 | 2.76e-31 | 7.56e-38 | 1.87e-37 | 5.32e-14 | |
| P_fus_TT | 1.63e+05 | 4.93e-34 | 1.18e-39 | 3.23e-40 | 0.0 | |
| P_heating | 1.49e+08 | 2.00e+08 | 7.00e-17 | 5.00e+07 | ||
| P_LH | 1.21e+08 -> 9.30e-07 | 7.29e+07 | ||||
| P_loss | 1.49e+08 | 2.00e+08 | 7.00e-17 | 5.00e+07 | ||
| P_neutron | 8.83e+07 | 2.77e-31 | 7.59e-38 | 1.87e-37 | 5.32e-14 | |
| P_ohmic | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| P_rad | 2.97e+08 | |||||
| P_sep | 1.70e+08 | |||||
| P_sep_B_over_q95AR | 9.20e+06 | |||||
| P_sep_over_R | 1.89e+07 | |||||
| p_th | 7.30e+05 | 4.00e-15 | 0.0 | 5.66e+05 | 0.0 | |
| P_wall | 4.05e+06 | |||||
| q95 | 7.2 | 3.89 | 8.03 | |||
| q_a | 4.7 | |||||
| q_min | 3.5 | |||||
| Q_sci | 0.8796934228208686 | 10.0 | 40.0 | 12.0 | 0.0 | |
| R | 3.3 | 9.0 | 12.5 | 3.6 | 12.74 | |
| R_max | 4.430000000000064 | 11.900000000000633 | 13.749999865601232 | 3.6200564301496705 | 14.039999867234382 | |
| R_min | 2.170000000000081 | 6.100000000001074 | 11.249999862884746 | 3.5799435704374396 | 11.439999864439342 | |
| Rr_DDn | 9.43e+16 | 3.05e-22 | 7.58e-28 | 2.12e-28 | 1.85e-28 | |
| Rr_DDp | 1.00e+17 | 3.22e-22 | 9.46e-28 | 2.20e-28 | 1.95e-28 | |
| Rr_DHe3 | 7.05e+14 | 0.0 | 0.0008072600450569387 | 2.39e-05 | 2.00e-28 | |
| Rr_DT | 3.82e+19 | 1.22e-19 | 3.35e-26 | 8.27e-26 | 0.0 | |
| Rr_He3He3 | 1.02e+08 | 0.0 | 0.0 | 0.0 | 3.67e-33 | |
| Rr_THe3 | 3.71e+13 | 0.0 | 4.72e-05 | 1.23e-06 | 0.0 | |
| Rr_THe3_D | 1.53e+13 | 0.0 | 1.89e-05 | 5.08e-07 | 0.0 | |
| Rr_THe3_np | 2.18e+13 | 0.0 | 2.83e-05 | 7.23e-07 | 0.0 | |
| Rr_TT | 8.88e+16 | 2.72e-22 | 6.51e-28 | 1.78e-28 | 0.0 | |
| S_phi | 7.381148588075762 | 47.301477885030664 | 36.930714636788835 | |||
| sigmav_DDn | profile(n=51, mean=1.21e-24, min=1.21e-24, max=1.21e-24) | profile(n=51, mean=9.51e-25, min=9.51e-25, max=9.51e-25) | profile(n=51, mean=3.3e-23, min=3.3e-23, max=3.3e-23) | profile(n=51, mean=2.19e-25, min=2.19e-25, max=2.19e-25) | profile(n=51, mean=5.37e-25, min=5.37e-25, max=5.37e-25) | profile(n=51, mean=8.72e-25, min=8.72e-25, max=8.72e-25) |
| sigmav_DDp | profile(n=51, mean=1.28e-24, min=1.28e-24, max=1.28e-24) | profile(n=51, mean=1e-24, min=1e-24, max=1e-24) | profile(n=51, mean=4.11e-23, min=4.11e-23, max=4.11e-23) | profile(n=51, mean=2.24e-25, min=2.24e-25, max=2.24e-25) | profile(n=51, mean=5.58e-25, min=5.58e-25, max=5.58e-25) | profile(n=51, mean=9.18e-25, min=9.18e-25, max=9.18e-25) |
| sigmav_DHe3 | profile(n=51, mean=8.92e-25, min=8.92e-25, max=8.92e-25) | profile(n=51, mean=5.58e-25, min=5.58e-25, max=5.58e-25) | profile(n=51, mean=2.34e-22, min=2.34e-22, max=2.34e-22) | profile(n=51, mean=3.34e-26, min=3.34e-26, max=3.34e-26) | profile(n=51, mean=1.84e-25, min=1.84e-25, max=1.84e-25) | profile(n=51, mean=4.71e-25, min=4.71e-25, max=4.71e-25) |
| sigmav_DT | profile(n=51, mean=2.41e-22, min=2.41e-22, max=2.41e-22) | profile(n=51, mean=1.91e-22, min=1.91e-22, max=1.91e-22) | profile(n=51, mean=7.28e-22, min=7.28e-22, max=7.28e-22) | profile(n=51, mean=3.82e-23, min=3.82e-23, max=3.82e-23) | profile(n=51, mean=1.05e-22, min=1.05e-22, max=1.05e-22) | profile(n=51, mean=1.75e-22, min=1.75e-22, max=1.75e-22) |
| sigmav_He3He3 | profile(n=51, mean=5.05e-29, min=5.05e-29, max=5.05e-29) | profile(n=51, mean=2.3e-29, min=2.3e-29, max=2.3e-29) | profile(n=51, mean=3.45e-24, min=3.45e-24, max=3.45e-24) | profile(n=51, mean=1.87e-31, min=1.87e-31, max=1.87e-31) | profile(n=51, mean=3.51e-30, min=3.51e-30, max=3.51e-30) | profile(n=51, mean=1.73e-29, min=1.73e-29, max=1.73e-29) |
| sigmav_THe3 | profile(n=51, mean=5.94e-26, min=5.94e-26, max=5.94e-26) | profile(n=51, mean=3.53e-26, min=3.53e-26, max=3.53e-26) | profile(n=51, mean=5.79e-23, min=5.79e-23, max=5.79e-23) | profile(n=51, mean=1.47e-27, min=1.47e-27, max=1.47e-27) | profile(n=51, mean=1.02e-26, min=1.02e-26, max=1.02e-26) | profile(n=51, mean=2.93e-26, min=2.93e-26, max=2.93e-26) |
| sigmav_THe3_D | profile(n=51, mean=1.9e-26, min=1.9e-26, max=1.9e-26) | profile(n=51, mean=1.19e-26, min=1.19e-26, max=1.19e-26) | profile(n=51, mean=5.48e-24, min=5.48e-24, max=5.48e-24) | profile(n=51, mean=6.81e-28, min=6.81e-28, max=6.81e-28) | profile(n=51, mean=3.92e-27, min=3.92e-27, max=3.92e-27) | profile(n=51, mean=1.01e-26, min=1.01e-26, max=1.01e-26) |
| sigmav_THe3_np | profile(n=51, mean=2.72e-26, min=2.72e-26, max=2.72e-26) | profile(n=51, mean=1.71e-26, min=1.71e-26, max=1.71e-26) | profile(n=51, mean=8.2e-24, min=8.2e-24, max=8.2e-24) | profile(n=51, mean=9.68e-28, min=9.68e-28, max=9.68e-28) | profile(n=51, mean=5.59e-27, min=5.59e-27, max=5.59e-27) | profile(n=51, mean=1.44e-26, min=1.44e-26, max=1.44e-26) |
| sigmav_TT | profile(n=51, mean=1.1e-24, min=1.1e-24, max=1.1e-24) | profile(n=51, mean=8.48e-25, min=8.48e-25, max=8.48e-25) | profile(n=51, mean=2.83e-23, min=2.83e-23, max=2.83e-23) | profile(n=51, mean=1.65e-25, min=1.65e-25, max=1.65e-25) | profile(n=51, mean=4.52e-25, min=4.52e-25, max=4.52e-25) | profile(n=51, mean=7.72e-25, min=7.72e-25, max=7.72e-25) |
| squareness | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| sudo_margin | -2.06e+20 | |||||
| T0 | 27.0 | |||||
| T_avg | 14.0 | 12.49 | 150.0 | 6.8 | 9.69 | 12.0 |
| T_e | profile(n=51, mean=14, min=14, max=14) | profile(n=51, mean=12.5, min=12.5, max=12.5) | profile(n=51, mean=150, min=150, max=150) | profile(n=51, mean=6.8, min=6.8, max=6.8) | profile(n=51, mean=9.69, min=9.69, max=9.69) | profile(n=51, mean=12, min=12, max=12) |
| T_i | profile(n=51, mean=14, min=14, max=14) | profile(n=51, mean=12.5, min=12.5, max=12.5) | profile(n=51, mean=150, min=150, max=150) | profile(n=51, mean=6.8, min=6.8, max=6.8) | profile(n=51, mean=9.69, min=9.69, max=9.69) | profile(n=51, mean=12, min=12, max=12) |
| tau_E | 0.64 -> 1.1118535173165673 | 7.71e-20 | 1.7 | 8343.576448332795 | 1.46 -> 0.0 | |
| tau_p | 1.0 | 11.0 | ||||
| tau_p_D | 1.0 | 11.0 | ||||
| tau_p_He3 | 1.0 | 11.0 | ||||
| tau_p_He4 | 1.0 | 11.0 | ||||
| tau_p_T | 1.0 | 11.0 | ||||
| tau_pulse | 7200.0 | |||||
| TBR | 1.1 | 1.3 | ||||
| troyon_margin | -0.0020080800307810745 | -0.0031334588678357082 | 0.13899001263867825 | |||
| V_p | 153.0 | 2568.1764898328174 | 46.0 -> 7.12e+11 | 788.9464369689385 | 425.0 | |
| W_th | 1.66e+08 | 1.54e-11 | 0.0 | 6.70e+08 | 0.0 | |
| Z_eff | 2.12 | 2.31 | 1.2 |
Warnings for Hammir2024: - diagnose failed: too many values to unpack (expected 3) Warnings for STEP2024EBCC: - diagnose failed: too many values to unpack (expected 3) Warnings for DEMO_2022: - diagnose failed: too many values to unpack (expected 3) Warnings for InfinityTwo2025: - diagnose failed: too many values to unpack (expected 3) Warnings for Stellaris2025: - diagnose failed: too many values to unpack (expected 3) Warnings for ARC_2015: - diagnose failed: too many values to unpack (expected 3)
In [4]:
Copied!
# Display reactor info using the new __repr__ method
print("Loaded reactors:")
for rid, reactor in sorted(reactors.items()):
print(f" {reactor}")
# Display reactor info using the new __repr__ method
print("Loaded reactors:")
for rid, reactor in sorted(reactors.items()):
print(f" {reactor}")
Loaded reactors: Reactor(id='ARC_2015', name='ARC 2015', org='MIT', year=2015, 101 variables, 97 relations) Reactor(id='DEMO_2022', name='EU-DEMO 2022', org='EUROFUSION', year=2022, 94 variables, 99 relations) Reactor(id='Hammir2024', name='Hammir 2024', org='Realta', year=2024, 57 variables, 89 relations) Reactor(id='InfinityTwo2025', name='INFINITY TWO 2025', org='TypeOne Energy', year=2025, 39 variables, 91 relations) Reactor(id='STEP2024EBCC', name='STEP 2024 EB-CC', org='UKAEA', year=2024, 91 variables, 104 relations) Reactor(id='Stellaris2025', name='Stellaris 2025 - Point A', org='Proxima', year=2025, 84 variables, 91 relations)
In [5]:
Copied!
# Prepare reactors for plotting (use all loaded reactors)
plot_reactors = reactors
log(f"Plotting all reactors: {', '.join(sorted(plot_reactors))}")
# Prepare reactors for plotting (use all loaded reactors)
plot_reactors = reactors
log(f"Plotting all reactors: {', '.join(sorted(plot_reactors))}")
Plotting all reactors: ARC_2015, DEMO_2022, Hammir2024, InfinityTwo2025, STEP2024EBCC, Stellaris2025
In [6]:
Copied!
# Plot plasma cross-sections for all loaded reactors
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(7, 6))
plotted = 0
for name, reactor in sorted(plot_reactors.items()):
try:
reactor.plot_cross_sections(ax=ax, label=name)
plotted += 1
except Exception as e:
log(f"Skipping {name}: {e}")
if plotted:
ax.legend()
plt.title('Reactor Plasma Cross-Sections (95% flux surface)')
plt.show()
else:
log("No reactors had sufficient geometry data to plot.")
# Plot plasma cross-sections for all loaded reactors
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(7, 6))
plotted = 0
for name, reactor in sorted(plot_reactors.items()):
try:
reactor.plot_cross_sections(ax=ax, label=name)
plotted += 1
except Exception as e:
log(f"Skipping {name}: {e}")
if plotted:
ax.legend()
plt.title('Reactor Plasma Cross-Sections (95% flux surface)')
plt.show()
else:
log("No reactors had sufficient geometry data to plot.")
Skipping Hammir2024: Missing 95% geometry variables for plotting: R, a, kappa_95, delta_95 Skipping InfinityTwo2025: Missing 95% geometry variables for plotting: kappa_95, delta_95 Skipping Stellaris2025: Missing 95% geometry variables for plotting: kappa_95, delta_95
POPCON scan example (DEMO)¶
In [7]:
Copied!
import numpy as np
from fusdb.registry.reactor_defaults import apply_reactor_defaults
# POPCON scan for DEMO over temperature and density.
demo = Reactor.from_yaml(root / "reactors" / "DEMO_2022" / "reactor.yaml")
scan_T = np.linspace(6.0, 20.0, 40)
scan_n = np.linspace(0.6e20, 1.4e20, 40)
# Ensure scan axes exist as explicit scalar inputs.
for name, first_value in (("n_avg", scan_n[0]), ("T_avg", scan_T[0])):
var = demo.variables_dict.get(name)
if var is None:
var = make_variable(name=name, ndim=0, input_source="explicit")
demo.variables_dict[name] = var
var.input_value = None
var.current_value = None
var.add_value(float(first_value), as_input=True)
var.input_source = "explicit"
# Clear scalar inputs that should follow averages.
for name in ("n_e", "n_i", "T_e", "T_i"):
var = demo.variables_dict.get(name)
if var is None:
continue
var.input_value = None
var.current_value = None
var.input_source = None
# Refresh defaults/relations after clearing inputs.
new_defaults = apply_reactor_defaults(demo.variables_dict, relations=demo.default_relations)
seen = {(rel.name, (rel._preferred_target if rel._preferred_target is not None else next(iter(rel.numeric_functions), None))) for rel in demo.default_relations}
for rel in new_defaults:
key = (rel.name, (rel._preferred_target if rel._preferred_target is not None else next(iter(rel.numeric_functions), None)))
if key in seen:
continue
demo.default_relations.append(rel)
seen.add(key)
demo.relations = list(demo._ordered_relations())
demo.solve()
fill_var = "P_fus"
contour_vars = ["Q_sci", "P_LH", "n_GW"] # P_aux is fixed in DEMO YAML.
scan_outputs = [fill_var, *contour_vars]
contour_counts = {name: 15 for name in contour_vars}
result = demo.popcon(
{"T_avg": scan_T, "n_avg": scan_n},
outputs=scan_outputs,
)
ax = demo.plot_popcon(
result,
x="T_avg",
y="n_avg",
fill=fill_var,
contours=contour_vars,
contour_counts=contour_counts,
constraint_contours=False,
)
ax.set_xlabel("<T_e> [keV]")
ax.set_ylabel("<n_e> [1/m^3]")
ax.set_title("DEMO POPCON: P_fus")
import numpy as np
from fusdb.registry.reactor_defaults import apply_reactor_defaults
# POPCON scan for DEMO over temperature and density.
demo = Reactor.from_yaml(root / "reactors" / "DEMO_2022" / "reactor.yaml")
scan_T = np.linspace(6.0, 20.0, 40)
scan_n = np.linspace(0.6e20, 1.4e20, 40)
# Ensure scan axes exist as explicit scalar inputs.
for name, first_value in (("n_avg", scan_n[0]), ("T_avg", scan_T[0])):
var = demo.variables_dict.get(name)
if var is None:
var = make_variable(name=name, ndim=0, input_source="explicit")
demo.variables_dict[name] = var
var.input_value = None
var.current_value = None
var.add_value(float(first_value), as_input=True)
var.input_source = "explicit"
# Clear scalar inputs that should follow averages.
for name in ("n_e", "n_i", "T_e", "T_i"):
var = demo.variables_dict.get(name)
if var is None:
continue
var.input_value = None
var.current_value = None
var.input_source = None
# Refresh defaults/relations after clearing inputs.
new_defaults = apply_reactor_defaults(demo.variables_dict, relations=demo.default_relations)
seen = {(rel.name, (rel._preferred_target if rel._preferred_target is not None else next(iter(rel.numeric_functions), None))) for rel in demo.default_relations}
for rel in new_defaults:
key = (rel.name, (rel._preferred_target if rel._preferred_target is not None else next(iter(rel.numeric_functions), None)))
if key in seen:
continue
demo.default_relations.append(rel)
seen.add(key)
demo.relations = list(demo._ordered_relations())
demo.solve()
fill_var = "P_fus"
contour_vars = ["Q_sci", "P_LH", "n_GW"] # P_aux is fixed in DEMO YAML.
scan_outputs = [fill_var, *contour_vars]
contour_counts = {name: 15 for name in contour_vars}
result = demo.popcon(
{"T_avg": scan_T, "n_avg": scan_n},
outputs=scan_outputs,
)
ax = demo.plot_popcon(
result,
x="T_avg",
y="n_avg",
fill=fill_var,
contours=contour_vars,
contour_counts=contour_counts,
constraint_contours=False,
)
ax.set_xlabel(" [keV]")
ax.set_ylabel(" [1/m^3]")
ax.set_title("DEMO POPCON: P_fus")
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) Cell In[7], line 32 30 # Refresh defaults/relations after clearing inputs. 31 new_defaults = apply_reactor_defaults(demo.variables_dict, relations=demo.default_relations) ---> 32 seen = {(rel.name, (rel._preferred_target if rel._preferred_target is not None else next(iter(rel.numeric_functions), None))) for rel in demo.default_relations} 33 for rel in new_defaults: 34 key = (rel.name, (rel._preferred_target if rel._preferred_target is not None else next(iter(rel.numeric_functions), None))) Cell In[7], line 32, in <setcomp>(.0) 30 # Refresh defaults/relations after clearing inputs. 31 new_defaults = apply_reactor_defaults(demo.variables_dict, relations=demo.default_relations) ---> 32 seen = {(rel.name, (rel._preferred_target if rel._preferred_target is not None else next(iter(rel.numeric_functions), None))) for rel in demo.default_relations} 33 for rel in new_defaults: 34 key = (rel.name, (rel._preferred_target if rel._preferred_target is not None else next(iter(rel.numeric_functions), None))) AttributeError: 'Relation' object has no attribute '_preferred_target'