์•ˆ๋ณด๋ฉด ์†ํ•ด ์†Œํ˜• ์›์ž๋ ฅ๋ฐœ์ „์†Œ ๋ชจ๋“ˆ ๋„์›€์ด ํ”„๋กœ๊ทธ๋žจ Don't Miss Out: Small Modular Reactor Helper Program"

SMR ์†Œํ˜• ๋ชจ๋“ˆํ™” ์›์ž๋ ฅ๋ฐœ์ „์†Œ ํ—ฌํผ ํ”„๋กœ๊ทธ๋žจ | NuScale & TerraPower ๋ฒค์น˜๋งˆํ‚น
⚛ SMR Helper Program v1.0 — NuScale × TerraPower Benchmarked

์†Œํ˜• ๋ชจ๋“ˆํ™” ์›์ž๋ ฅ๋ฐœ์ „์†Œ
ํ—ฌํผ ํ”„๋กœ๊ทธ๋žจ

NuScale Ansys ์Šคํƒ€์ผ ์—ด์ „๋‹ฌ ๋ถ„์„ + TerraPower ARMI Python ๊ธฐ๋ฐ˜
Reactor Model Hub-and-Spoke ์ž๋™ํ™” ๊ตฌํ˜„ ๊ฐ€์ด๋“œ

#1 NuScale Power — Ansys ๋ฉ€ํ‹ฐํ”ผ์ง์Šค
#2 TerraPower — ARMI ์˜คํ”ˆ์†Œ์Šค ํ”„๋ ˆ์ž„์›Œํฌ
๋ฒค์น˜๋งˆํ‚น ๋ถ„์„ — ์„ธ๊ณ„ 1·2์œ„ SMR ํ”„๋ ˆ์ž„์›Œํฌ
NuScale Ansys vs TerraPower ARMI ํ•ต์‹ฌ ์•„ํ‚คํ…์ฒ˜ ๋น„๊ต
๐Ÿ’ก
ํ—ฌํผ ํ”„๋กœ๊ทธ๋žจ ๋ชฉํ‘œ: NuScale์˜ Ansys ์—ด·๊ตฌ์กฐ·์•ˆ์ „ ๋ถ„์„ ํŒŒ์ดํ”„๋ผ์ธ๊ณผ TerraPower ARMI์˜ Hub-and-Spoke ํ”Œ๋Ÿฌ๊ทธ์ธ ์•„ํ‚คํ…์ฒ˜๋ฅผ ํŒŒ์ด์ฌ ์˜คํ”ˆ์†Œ์Šค๋กœ ์žฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
ํ•ญ๋ชฉ #1 NuScale Power #2 TerraPower
ํ•ต์‹ฌ ํ”„๋ ˆ์ž„์›Œํฌ Ansys ๋ฉ€ํ‹ฐํ”ผ์ง์Šค (์ƒ์šฉ) ARMI — Python ์˜คํ”ˆ์†Œ์Šค
์‹œ๋ฎฌ ๋ฐฉ์‹ FEM ์—ด·๊ตฌ์กฐ·์œ ์ฒด ์—ฐ์„ฑ ํ•ด์„ Hub-and-Spoke ํ”Œ๋Ÿฌ๊ทธ์ธ ์ปค๋„
์›์ž๋กœ ๋ชจ๋ธ NuScale Module (SMR 77MW) Natrium (Na-๋ƒ‰๊ฐ SFR 345MW)
์ธ์ฆ ์ƒํƒœ NRC DCA ์Šน์ธ (2022) DOE ์‹œ๋ฒ” ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ ์ค‘
ํŒŒ๋ผ๋ฏธํ„ฐ ์Šค์œ• Ansys Parametric Design Language Python multiprocessing ๋ณ‘๋ ฌ
์˜คํ”ˆ์†Œ์Šค ๋Œ€์ฒด NumPy/SciPy conjugate heat transfer github.com/terrapower/armi
์ถœ๋ ฅ ํ˜•์‹ IGES/STEP + Ansys Result File CSV / HDF5 / ์ปค์Šคํ…€ DB
๐Ÿ—
ํ—ฌํผ ํ”„๋กœ๊ทธ๋žจ ํ•ต์‹ฌ ๊ธฐ๋Šฅ 6๊ฐ€์ง€
๐Ÿ—
① ์›์ž๋กœ ๋ชจ๋ธ ์ƒ์„ฑ
YAML ์ž…๋ ฅ → ์œก๊ฐํ˜•/์‚ฌ๊ฐํ˜• ์ฝ”์–ด ์ž๋™ ์ƒ์„ฑ. ํ•ต์—ฐ๋ฃŒ ์ง‘ํ•ฉ์ฒด·์ œ์–ด๋ด‰·๋ƒ‰๊ฐ์žฌ ์ฑ„๋„ ํŒŒ๋ผ๋ฏธํ„ฐํ™”
๐ŸŒก
② ์—ด์œ ์ฒด ์‹œ๋ฎฌ๋ ˆ์ด์…˜
NuScale Ansys ๋ชจ๋ฐฉ conjugate heat transfer. Nusselt ์ˆ˜ ์ƒ๊ด€๊ด€๊ณ„๋กœ ๋ƒ‰๊ฐ์žฌ ์˜จ๋„ ๋ถ„ํฌ ๊ณ„์‚ฐ
③ ์ค‘์„ฑ์ž ๋ฌผ๋ฆฌ ์ปค๋„
4-์ธ์ž ๊ณต์‹์œผ๋กœ ์ž„๊ณ„ ์กฐ๊ฑด(k-eff) ๊ณ„์‚ฐ. ํ•ต์—ฐ๋ฃŒ ์†Œ์ง„ ๋ชจ๋ธ๋ง(Bateman ๋ฐฉ์ •์‹)
๐Ÿ›ก
④ ์•ˆ์ „ ๋ถ„์„ ๋ชจ๋“ˆ
DNBR · ์ตœ๋Œ€ ์—ฐ๋ฃŒ ์˜จ๋„ · ์••๋ ฅ ๋งˆ์ง„ ๊ฒ€์ฆ. NRC 10 CFR 50 ๊ธฐ์ค€ ์ž๋™ ์ฑ„ํฌ
๐Ÿ”„
⑤ ํŒŒ๋ผ๋ฏธํ„ฐ ์Šค์œ•
Python multiprocessing 1000+ ์ผ€์ด์Šค ๋ณ‘๋ ฌ ์‹คํ–‰. ARMI ์Šคํƒ€์ผ ์ตœ์ ํ™” ๋ฃจํ”„
๐Ÿ“Š
⑥ ์ž๋™ํ™” ๋ฆฌํฌํŠธ
YAML ์ž…๋ ฅ → ์‹œ๋ฎฌ ์‹คํ–‰ → CSV/PNG ์ถœ๋ ฅ ์™„์ „ ์ž๋™ํ™” ์›Œํฌํ”Œ๋กœ
๐Ÿ“ฆ
ํ™˜๊ฒฝ ์„ค์น˜ (ARMI ์˜์กด์„ฑ ๋ฒค์น˜๋งˆํ‚น)
bash — ํ™˜๊ฒฝ ์„ค์น˜
# 1. ๊ฐ€์ƒํ™˜๊ฒฝ ์ƒ์„ฑ
python -m venv smr_env
source smr_env/bin/activate  # Windows: smr_env\Scripts\activate

# 2. ํ•ต์‹ฌ ์˜์กด์„ฑ ์„ค์น˜ (ARMI ๋ฒค์น˜๋งˆํ‚น)
pip install numpy scipy matplotlib pandas pyyaml

# 3. ์„ ํƒ: TerraPower ARMI ์›๋ณธ ํด๋ก 
git clone https://github.com/terrapower/armi.git
cd armi && pip install -e .

# 4. ์„ ํƒ: ์‹œ๊ฐํ™” ํ™•์žฅ
pip install plotly seaborn h5py
๐Ÿ”ฅ
SMR ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ (์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ)
NuScale ์—ด์œ ์ฒด + TerraPower ARMI ์Šคํƒ€์ผ ์‹ค์‹œ๊ฐ„ ๊ณ„์‚ฐ
⚛ SMR Core Thermal-Hydraulics Simulator v1.0 READY
PRESSURE VESSEL CR COLD LEG HOT LEG STEAM GENERATOR 285°C THERMAL POWER 77.0 MWth
์—ฐ๋ฃŒ ์˜จ๋„ (T_fuel)
620°C
๋ƒ‰๊ฐ์žฌ ์œ ์† (Flow)
60%
์ œ์–ด๋ด‰ ์‚ฝ์ž… (CR)
30%
๋†์ถ•๋„ (Enrichment)
5.0%
k-eff (์ž„๊ณ„)
1.024
๋ฌด์ฐจ์›
์—ด์ถœ๋ ฅ
77.0
MWth
DNBR
2.41
≥ 1.30 ์•ˆ์ „
๋ƒ‰๊ฐ์žฌ ์ถœ๊ตฌ T
285
°C
์••๋ ฅ ๋งˆ์ง„
18.2
% ์—ฌ์œ 
์•ˆ์ „ ์ƒํƒœ
✅ ์ •์ƒ
All limits OK
[00:00:00]SMR Helper Program v1.0 ์ดˆ๊ธฐํ™” ์™„๋ฃŒ
[00:00:00]NuScale ์—ด์œ ์ฒด ๋ชจ๋“ˆ ๋กœ๋“œ๋จ
[00:00:00]ARMI Reactor ํด๋ž˜์Šค ์ดˆ๊ธฐํ™”๋จ
[00:00:00]ํŒŒ๋ผ๋ฏธํ„ฐ ์Šฌ๋ผ์ด๋”๋ฅผ ์กฐ์ •ํ•˜๊ณ  ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ์‹คํ–‰ํ•˜์„ธ์š”.
๐Ÿ’ป
์ฝ”๋“œ ๊ตฌ์กฐ ์„ค๊ณ„ — ARMI Hub-and-Spoke ์•„ํ‚คํ…์ฒ˜
TerraPower ARMI ๋ฒค์น˜๋งˆํ‚น Python ๊ตฌํ˜„์ฒด
STEP 1 — Reactor ํด๋ž˜์Šค (Hub)
ARMI์˜ ์ค‘์•™ Reactor ํด๋ž˜์Šค๋ฅผ ๋ชจ๋ฐฉํ•œ ์ƒํƒœ ๊ด€๋ฆฌ ํ—ˆ๋ธŒ. ํ•ต์—ฐ๋ฃŒ·๋ƒ‰๊ฐ์žฌ ๋ฐ€๋„·์˜จ๋„ ๋“ฑ ์ „์ฒด ์›์ž๋กœ ์ƒํƒœ๋ฅผ ๋‹จ์ผ ๊ฐ์ฒด๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
python — reactor_core.py (ARMI Hub ๋ชจ๋ฐฉ)
import numpy as np
import yaml
from dataclasses import dataclass, field
from typing import Dict, List, Optional

# ============================================================
# SMR Helper — Reactor Core (TerraPower ARMI ๋ฒค์น˜๋งˆํ‚น)
# Hub-and-Spoke ์•„ํ‚คํ…์ฒ˜: Reactor๊ฐ€ ๋ชจ๋“  ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ ํ—ˆ๋ธŒ
# ============================================================

@dataclass
class FuelAssembly:
    """๋‹จ์ผ ํ•ต์—ฐ๋ฃŒ ์ง‘ํ•ฉ์ฒด ์ƒํƒœ (ARMI Block ๋ชจ๋ฐฉ)"""
    assembly_id: int
    enrichment: float       # U-235 ๋†์ถ•๋„ (%)
    burnup: float = 0.0       # ์—ฐ์†Œ๋„ (MWd/kgU)
    temp_fuel: float = 620.0  # ์—ฐ๋ฃŒ ์˜จ๋„ (°C)
    temp_clad: float = 340.0  # ํ”ผ๋ณต๊ด€ ์˜จ๋„ (°C)
    linear_power: float = 0.0  # ์„ ํ˜• ์ถœ๋ ฅ ๋ฐ€๋„ (kW/m)


@dataclass
class ReactorState:
    """์ „์ฒด ์›์ž๋กœ ์ƒํƒœ ์Šค๋ƒ…์ƒท"""
    power_mwth: float = 77.0   # ์—ด์ถœ๋ ฅ (MW)
    keff: float = 1.0           # ์œ ํšจ ์ฆ๋ฐฐ๊ณ„์ˆ˜
    t_inlet: float = 258.0     # ๋ƒ‰๊ฐ์žฌ ์ž…๊ตฌ ์˜จ๋„ (°C)
    t_outlet: float = 285.0    # ๋ƒ‰๊ฐ์žฌ ์ถœ๊ตฌ ์˜จ๋„ (°C)
    flow_rate: float = 587.0   # ์งˆ๋Ÿ‰ ์œ ๋Ÿ‰ (kg/s)
    pressure: float = 12.76    # 1์ฐจ๊ณ„ ์••๋ ฅ (MPa)
    dnbr_min: float = 2.41     # ์ตœ์†Œ DNBR


class Reactor:
    """
    SMR Helper ์ค‘์•™ Reactor ํด๋ž˜์Šค
    ─ TerraPower ARMI์˜ Reactor Hub ๋ชจ๋ฐฉ
    ─ ๋ชจ๋“  ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ์ด ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์ƒํ˜ธ์ž‘์šฉ
    """

    def __init__(self, geom: str = 'hex', config_path: Optional[str] = None):
        self.geom = geom
        self.state = ReactorState()
        self.assemblies: List[FuelAssembly] = []
        self.plugins: Dict = {}
        self._history: List[ReactorState] = []

        if config_path:
            self._load_yaml(config_path)
        else:
            self._init_default_core()

    def _init_default_core(self):
        """NuScale SMR ๊ธฐ๋ณธ 77MWth ์ฝ”์–ด ์ดˆ๊ธฐํ™”"""
        n_assemblies = 37  # NuScale ํ‘œ์ค€ ์ง‘ํ•ฉ์ฒด ์ˆ˜
        for i in range(n_assemblies):
            enr = 4.95 if i % 3 != 0 else 2.35  # 2์กด ์žฅ์ „
            self.assemblies.append(FuelAssembly(i, enrichment=enr))

    def _load_yaml(self, path: str):
        with open(path) as f:
            cfg = yaml.safe_load(f)
        self.state.power_mwth = cfg.get('power_mwth', 77.0)
        enr = cfg.get('enrichment', 4.95)
        n = cfg.get('n_assemblies', 37)
        self.assemblies = [FuelAssembly(i, enrichment=enr) for i in range(n)]

    def register_plugin(self, name: str, plugin):
        """Hub-and-Spoke: ํ”Œ๋Ÿฌ๊ทธ์ธ ๋“ฑ๋ก"""
        self.plugins[name] = plugin
        plugin.reactor = self  # ์—ญ์ฐธ์กฐ

    def run(self) -> ReactorState:
        """์ „์ฒด ๋ฌผ๋ฆฌ ํ•ด์„ ์ˆœ์ฐจ ์‹คํ–‰"""
        for name, plugin in self.plugins.items():
            plugin.execute()
        self._history.append(ReactorState(**vars(self.state)))
        return self.state

    def avg_enrichment(self) -> float:
        return np.mean([a.enrichment for a in self.assemblies])
STEP 2 — ์—ด์œ ์ฒด ํ”Œ๋Ÿฌ๊ทธ์ธ (NuScale Ansys ๋ชจ๋ฐฉ)
Dittus-Boelter ์ƒ๊ด€๊ด€๊ณ„ + DNBR ๊ณ„์‚ฐ. NuScale์ด Ansys์—์„œ ์ˆ˜ํ–‰ํ•˜๋Š” conjugate heat transfer๋ฅผ NumPy/SciPy๋กœ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
python — thermal_plugin.py (NuScale Ansys ์—ด์œ ์ฒด ๋ชจ๋ฐฉ)
import numpy as np

class ThermalHydraulicsPlugin:
    """
    NuScale Ansys ์Šคํƒ€์ผ ์—ด์œ ์ฒด ๋ถ„์„ ํ”Œ๋Ÿฌ๊ทธ์ธ
    ─ Dittus-Boelter ์ƒ๊ด€๊ด€๊ณ„ (๊ฐ•์ œ ๋Œ€๋ฅ˜ Nu์ˆ˜)
    ─ DNBR: Departure from Nucleate Boiling Ratio
    ─ ๋ƒ‰๊ฐ์žฌ ์ถœ๊ตฌ ์˜จ๋„, ์—ฐ๋ฃŒ/ํ”ผ๋ณต๊ด€ ์˜จ๋„ ๋ถ„ํฌ ๊ณ„์‚ฐ
    """

    # ── NuScale SMR ์„ค๊ณ„ ์ƒ์ˆ˜ ──
    CP_WATER  = 5200    # ๋ƒ‰๊ฐ์žฌ ๋น„์—ด (J/kg·K) @ 285°C
    MU_WATER  = 9.5e-5  # ๋™์ ๋„ (Pa·s)
    K_WATER   = 0.58    # ์—ด์ „๋„๋„ (W/m·K)
    PR_WATER  = 0.86    # Prandtl ์ˆ˜
    D_HYDRO   = 0.012   # ์ˆ˜๋ ฅ ์ง๊ฒฝ (m)
    DNB_LIMIT = 1.3     # NRC ์ตœ์†Œ DNBR ๊ธฐ์ค€
    K_FUEL    = 3.0     # UO2 ์—ด์ „๋„๋„ (W/m·K)
    R_PELLET  = 0.00465 # ์—ฐ๋ฃŒ ํŽ ๋ › ๋ฐ˜๊ฒฝ (m)

    def __init__(self):
        self.reactor = None  # Hub๊ฐ€ ์ฃผ์ž…

    def execute(self):
        """ํ”Œ๋Ÿฌ๊ทธ์ธ ์‹คํ–‰ ์ง„์ž…์ """
        r = self.reactor
        m_dot = r.state.flow_rate
        q_total = r.state.power_mwth * 1e6  # W

        # 1. ๋ƒ‰๊ฐ์žฌ ์ถœ๊ตฌ ์˜จ๋„ (์—๋„ˆ์ง€ ๋ณด์กด)
        delta_T = q_total / (m_dot * self.CP_WATER)
        r.state.t_outlet = r.state.t_inlet + delta_T

        # 2. Dittus-Boelter Nu์ˆ˜ → ์—ด์ „๋‹ฌ ๊ณ„์ˆ˜
        Re = (m_dot * self.D_HYDRO) / (self.MU_WATER * 0.05)
        Nu = 0.023 * (Re ** 0.8) * (self.PR_WATER ** 0.4)
        h_conv = Nu * self.K_WATER / self.D_HYDRO  # W/m²·K

        # 3. ์ตœ๋Œ€ ์„ ํ˜• ์ถœ๋ ฅ ๋ฐ€๋„
        n_rods = len(r.assemblies) * 264
        rod_len = 2.0
        q_lin = q_total / (n_rods * rod_len)  # W/m

        # 4. DNBR ๊ณ„์‚ฐ (W-3 ์ƒ๊ด€๊ด€๊ณ„ ๋‹จ์ˆœํ™”)
        q_dnb = 3.154e6 * (r.state.pressure / 15.5) ** 0.5
        r.state.dnbr_min = q_dnb / max(q_lin / 0.05, 1)
        r.state.dnbr_min = min(r.state.dnbr_min, 5.0)  # ์ƒํ•œ ํด๋ฆฌํ•‘

        # 5. ์—ฐ๋ฃŒ ์ค‘์‹ฌ ์˜จ๋„
        q_triple = q_lin / (np.pi * self.R_PELLET ** 2)
        delta_T_fuel = q_triple * (self.R_PELLET ** 2) / (4 * self.K_FUEL)
        T_fuel_max = r.state.t_outlet + delta_T_fuel / 1000

        # 6. ๊ฐ ์ง‘ํ•ฉ์ฒด ์˜จ๋„ ์—…๋ฐ์ดํŠธ
        for asm in r.assemblies:
            factor = asm.enrichment / r.avg_enrichment()
            asm.temp_fuel = T_fuel_max * factor
            asm.linear_power = q_lin * factor / 1000  # kW/m

    def get_temp_distribution(self) -> np.ndarray:
        """NuScale ์Šคํƒ€์ผ ๋ฐ˜๊ฒฝ ๋ฐฉํ–ฅ ์˜จ๋„ ๋ถ„ํฌ ๋ฐ˜ํ™˜"""
        r_arr = np.linspace(0, self.R_PELLET, 50)
        T_surface = self.reactor.state.t_outlet + 50
        q3 = (self.reactor.state.power_mwth * 1e6 /
              (len(self.reactor.assemblies) * 264 * 2.0) /
              (np.pi * self.R_PELLET ** 2))
        return T_surface + (q3 / (4 * self.K_FUEL)) * (self.R_PELLET**2 - r_arr**2)
STEP 3 — ์ค‘์„ฑ์ž ๋ฌผ๋ฆฌ ํ”Œ๋Ÿฌ๊ทธ์ธ (4์ธ์ž ๊ณต์‹ + ์—ฐ์†Œ)
TerraPower ARMI์˜ ์ค‘์„ฑ์ž ๋ฌผ๋ฆฌ ์ปค๋„์„ 4์ธ์ž ๊ณต์‹์œผ๋กœ ๊ทผ์‚ฌ. k-eff ๊ณ„์‚ฐ ๋ฐ Bateman ๋ฐฉ์ •์‹ ๊ธฐ๋ฐ˜ ํ•ต์—ฐ๋ฃŒ ์†Œ์ง„ ๋ชจ๋ธ๋ง.
python — neutronics_plugin.py (ARMI ์ค‘์„ฑ์ž ๋ฌผ๋ฆฌ ๋ชจ๋ฐฉ)
import numpy as np
from scipy.integrate import odeint

class NeutronicsPlugin:
    """
    TerraPower ARMI ์ค‘์„ฑ์ž ๋ฌผ๋ฆฌ ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ชจ๋ฐฉ
    ─ 4์ธ์ž ๊ณต์‹: k∞ = ฮท × ฮต × p × f
    ─ k_eff = k∞ / (1 + M²·B²) with migration area M²
    ─ Bateman ODE: ํ•ต์—ฐ๋ฃŒ ์†Œ์ง„ (์—ฐ์†Œ๋„ → k_eff ๊ฐ์†Œ)
    """

    def __init__(self):
        self.reactor = None

    def four_factor(self, enrichment: float, burnup: float,
                     cr_fraction: float) -> float:
        """
        4์ธ์ž ๊ณต์‹์œผ๋กœ k_eff ๊ณ„์‚ฐ
        enrichment : U-235 ๋†์ถ•๋„ (%)
        burnup     : ์—ฐ์†Œ๋„ (MWd/kgU)
        cr_fraction: ์ œ์–ด๋ด‰ ์‚ฝ์ž… ๋น„์œจ (0~1)
        """
        # ฮท: U-235 ํ•ต๋ถ„์—ด ์ค‘์„ฑ์ž ์ˆ˜ (๋†์ถ•๋„ ์˜์กด)
        eta = 2.065 * (1 - np.exp(-enrichment / 3.0))

        # ฮต: ๊ณ ์† ํ•ต๋ถ„์—ด ์ธ์ž (์ƒ์ˆ˜ ๊ทผ์‚ฌ)
        epsilon = 1.048

        # p: ๊ณต๋ช… ํƒˆ์ถœ ํ™•๋ฅ  (์—ฐ์†Œ๋„ ์ฆ๊ฐ€ ์‹œ ๊ฐ์†Œ)
        p = 0.82 * np.exp(-burnup / 80.0)

        # f: ์—ด์ค‘์„ฑ์ž ์ด์šฉ๋ฅ  (๋†์ถ•๋„ ์ฆ๊ฐ€ → ์ฆ๊ฐ€)
        f = 0.71 + 0.018 * enrichment

        k_inf = eta * epsilon * p * f  # ๋ฌดํ•œ ์ฆ๋ฐฐ๊ณ„์ˆ˜

        # ๋น„๋ˆ„์„ค ํ™•๋ฅ  (์›ํ†ตํ˜• ์ฝ”์–ด, M² ≈ 50 cm²)
        M_sq = 50.0  # cm²
        B_sq = (2.405 / 150.0) ** 2 + (np.pi / 240.0) ** 2  # cm⁻²
        nonleak = 1.0 / (1 + M_sq * B_sq * 1e4)

        # ์ œ์–ด๋ด‰ ์Œ์˜ ๋ฐ˜์‘๋„ (CR ์‚ฝ์ž…)
        rho_cr = -0.025 * cr_fraction

        k_eff = k_inf * (1 - abs(rho_cr))
        return max(k_eff, 0.0)

    def execute(self):
        """Hub์—์„œ ํ˜ธ์ถœ๋˜๋Š” ์‹คํ–‰ ์ง„์ž…์ """
        r = self.reactor
        avg_enr = r.avg_enrichment()
        avg_bu  = np.mean([a.burnup for a in r.assemblies])
        cr_frac = getattr(r, 'cr_fraction', 0.3)

        r.state.keff = self.four_factor(avg_enr, avg_bu, cr_frac)

        # ์—ด์ถœ๋ ฅ์„ k_eff์™€ ์ž„๊ณ„ ์ƒํƒœ์— ๋”ฐ๋ผ ์กฐ์ •
        if r.state.keff > 1.0:
            r.state.power_mwth = 77.0 * r.state.keff
        else:
            r.state.power_mwth = 77.0 * r.state.keff * 0.95

    def burnup_step(self, dt_efpd: float = 1.0):
        """์—ฐ์†Œ๋„ ์ฆ๊ฐ€ (Bateman ๋ฐฉ์ •์‹ ๋‹จ์ˆœํ™”)"""
        flux = self.reactor.state.power_mwth / (77.0 * 37)
        for asm in self.reactor.assemblies:
            asm.burnup += flux * dt_efpd * 0.95
STEP 4 — ํŒŒ๋ผ๋ฏธํ„ฐ ์Šค์œ• ์ž๋™ํ™” (ARMI ์ตœ์ ํ™”)
Python multiprocessing์œผ๋กœ 1000+ ์ผ€์ด์Šค ๋ณ‘๋ ฌ ์‹คํ–‰. YAML ์ž…๋ ฅ → ๋ชจ๋ธ ์ƒ์„ฑ → ์‹œ๋ฎฌ → CSV/PNG ์ถœ๋ ฅ ์™„์ „ ์ž๋™ํ™”.
python — param_sweep.py (ARMI ํŒŒ๋ผ๋ฏธํ„ฐ ์Šค์œ• ์ž๋™ํ™”)
import multiprocessing as mp
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from reactor_core import Reactor
from thermal_plugin import ThermalHydraulicsPlugin
from neutronics_plugin import NeutronicsPlugin


def run_case(params: dict) -> dict:
    """๋‹จ์ผ ์ผ€์ด์Šค ์‹คํ–‰ (multiprocessing worker)"""
    r = Reactor(geom='hex')
    r.state.flow_rate = params['flow_rate']
    r.cr_fraction     = params['cr_fraction']
    for asm in r.assemblies:
        asm.enrichment = params['enrichment']

    neut = NeutronicsPlugin()
    th   = ThermalHydraulicsPlugin()
    r.register_plugin('neutronics', neut)
    r.register_plugin('thermal', th)
    state = r.run()

    return {
        'enrichment'  : params['enrichment'],
        'cr_fraction' : params['cr_fraction'],
        'flow_rate'   : params['flow_rate'],
        'keff'        : round(state.keff, 4),
        'power_mwth'  : round(state.power_mwth, 2),
        't_outlet'    : round(state.t_outlet, 1),
        'dnbr_min'    : round(state.dnbr_min, 3),
        'safe'        : state.dnbr_min >= 1.3 and state.t_outlet < 325,
    }


def parameter_sweep(n_cases: int = 1000) -> pd.DataFrame:
    """
    ARMI ์Šคํƒ€์ผ 1000+ ์ผ€์ด์Šค ๋ณ‘๋ ฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์Šค์œ•
    ─ ๋†์ถ•๋„, ์ œ์–ด๋ด‰ ์‚ฝ์ž…, ์œ ๋Ÿ‰ 3D ๊ฒฉ์ž ์Šค์œ•
    """
    enrichments = np.linspace(2.0, 10.0, 10)
    cr_fracs    = np.linspace(0.0, 0.8, 10)
    flows       = np.linspace(400, 700, 10)

    param_list = [
        {'enrichment': e, 'cr_fraction': c, 'flow_rate': f}
        for e in enrichments
        for c in cr_fracs
        for f in flows
    ]

    # multiprocessing ๋ณ‘๋ ฌ ์‹คํ–‰
    with mp.Pool(processes=mp.cpu_count()) as pool:
        results = pool.map(run_case, param_list[:n_cases])

    df = pd.DataFrame(results)
    df.to_csv('smr_sweep_results.csv', index=False)
    plot_sweep_results(df)
    return df


def plot_sweep_results(df: pd.DataFrame):
    """NuScale ์Šคํƒ€์ผ ํŒŒ๋ผ๋ฏธํ„ฐ ์Šค์œ• ์‹œ๊ฐํ™”"""
    fig, axes = plt.subplots(1, 3, figsize=(15, 5))
    fig.patch.set_facecolor('#0a0e1a')
    for ax in axes:
        ax.set_facecolor('#0f1525')

    axes[0].scatter(df['enrichment'], df['keff'],
                   c=df['cr_fraction'], cmap='viridis', s=4, alpha=0.7)
    axes[0].axhline(1.0, color='#ef4444', linestyle='--', lw=1)
    axes[0].set_xlabel('Enrichment (%)', color='#94a3b8')
    axes[0].set_ylabel('k-eff', color='#94a3b8')

    axes[1].scatter(df['power_mwth'], df['dnbr_min'],
                   c=df['safe'].astype(int),
                   cmap='RdYlGn', s=4, alpha=0.7)
    axes[1].axhline(1.3, color='#ef4444', linestyle='--', lw=1)

    axes[2].hist(df['t_outlet'], bins=40, color='#3b82f6', alpha=0.8)
    axes[2].set_xlabel('T_outlet (°C)', color='#94a3b8')

    plt.tight_layout()
    plt.savefig('smr_sweep.png', dpi=150, bbox_inches='tight')

# ── ์‹คํ–‰ ์ง„์ž…์  ──
if __name__ == '__main__':
    df = parameter_sweep(n_cases=1000)
    print(f"์•ˆ์ „ ์ผ€์ด์Šค: {df['safe'].sum()} / {len(df)} ({df['safe'].mean():.1%})")
๐Ÿ”Œ
Hub-and-Spoke ํ”Œ๋Ÿฌ๊ทธ์ธ ์‹œ์Šคํ…œ (ARMI ์•„ํ‚คํ…์ฒ˜)
๋ชจ๋“ˆ์„ ์„ ํƒ ํ™œ์„ฑํ™”ํ•˜์—ฌ ์ปค์Šคํ…€ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ
โ„น
TerraPower ARMI Hub-and-Spoke: ์ค‘์•™ Reactor ๊ฐ์ฒด(Hub)์— ํ”Œ๋Ÿฌ๊ทธ์ธ(Spoke)์„ ๋™์ ์œผ๋กœ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋˜๋ฉฐ Reactor ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.
NeutronicsPlugin
4์ธ์ž ๊ณต์‹ k-eff · Bateman ์—ฐ์†Œ · ๋ฐ˜์‘๋„ ๊ณ„์ˆ˜
๐ŸŒก
ThermalPlugin
Dittus-Boelter ์—ด์ „๋‹ฌ · DNBR · ์˜จ๋„ ๋ถ„ํฌ
๐Ÿ›ก
SafetyPlugin
NRC 10 CFR 50 · LOCA · RIA ์‚ฌ๊ณ  ๋ถ„์„
๐Ÿ”ฅ
BurnupPlugin
ORIGEN ์Šคํƒ€์ผ ํ•ต์ข… ๋ณ€ํ™˜ · ์ž”๋ฅ˜ ๋ฐœ์—ด
๐Ÿ’ฐ
EconomicsPlugin
LCOE ๊ณ„์‚ฐ · ์—ฐ๋ฃŒ ๋น„์šฉ ์ตœ์ ํ™” · ROI ๋ถ„์„
๐Ÿ“Š
VisualizPlugin
Matplotlib ์—ด์ง€๋„ · ์ค‘์„ฑ์ž์† ๋ถ„ํฌ · ์ฝ”์–ด ๋งต
ํ™œ์„ฑ ํ”Œ๋Ÿฌ๊ทธ์ธ ํŒŒ์ดํ”„๋ผ์ธ (์‹คํ–‰ ์ˆœ์„œ):
๐Ÿ“„
YAML ์ž…๋ ฅ ํŒŒ์ผ ์—๋””ํ„ฐ (์ž๋™ํ™” ์›Œํฌํ”Œ๋กœ)
smr_config.yaml — ์›์ž๋กœ ์ž…๋ ฅ ํŒŒ์ผ
python — main.py (ARMI ์Šคํƒ€์ผ Hub ํ”Œ๋Ÿฌ๊ทธ์ธ ๋“ฑ๋ก)
from reactor_core import Reactor
from neutronics_plugin import NeutronicsPlugin
from thermal_plugin import ThermalHydraulicsPlugin
from safety_plugin import SafetyPlugin
import yaml

# ── 1. YAML ์ž…๋ ฅ์œผ๋กœ Reactor(Hub) ์ƒ์„ฑ ──
reactor = Reactor(geom='hex', config_path='smr_config.yaml')

# ── 2. ํ”Œ๋Ÿฌ๊ทธ์ธ(Spoke) ๋“ฑ๋ก (ARMI Hub-and-Spoke) ──
reactor.register_plugin('neutronics', NeutronicsPlugin())
reactor.register_plugin('thermal',    ThermalHydraulicsPlugin())
reactor.register_plugin('safety',     SafetyPlugin())

# ── 3. ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹คํ–‰ ──
state = reactor.run()

# ── 4. ๊ฒฐ๊ณผ ์ถœ๋ ฅ ──
print(f"k-eff      : {state.keff:.4f}")
print(f"์—ด์ถœ๋ ฅ     : {state.power_mwth:.1f} MWth")
print(f"T_outlet   : {state.t_outlet:.1f} °C")
print(f"DNBR_min   : {state.dnbr_min:.3f}")
print(f"์•ˆ์ „ ํŒ์ •  : {'OK ✅' if state.dnbr_min >= 1.3 else 'FAIL ❌'}")

# ── 5. ํŒŒ๋ผ๋ฏธํ„ฐ ์Šค์œ• (1000 ์ผ€์ด์Šค ๋ณ‘๋ ฌ) ──
from param_sweep import parameter_sweep
df = parameter_sweep(n_cases=1000)
print(df.describe())
๐Ÿ›ก
์•ˆ์ „ ๋ถ„์„ ๋ชจ๋“ˆ (NRC 10 CFR 50 ๊ธฐ์ค€)
NuScale NRC ์ธ์ฆ ๊ธฐ์ค€ ์ ์šฉ ์ž๋™ ์•ˆ์ „ ๊ฒ€์ฆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ
๊ต์œก·์—ฐ๊ตฌ ๋ชฉ์  ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ: ๋ณธ ํ—ฌํผ ํ”„๋กœ๊ทธ๋žจ์€ ๊ฐœ๋… ํ•™์Šต ๋ฐ ์ฝ”๋“œ ์•„ํ‚คํ…์ฒ˜ ์ดํ•ด๋ฅผ ์œ„ํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ์›์ž๋ ฅ ์•ˆ์ „ ๋ถ„์„์€ NRC/IAEA ์ธ์ฆ ์ฝ”๋“œ(RELAP5, TRACE, PARCS)๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์•ˆ์ „ ๊ธฐ์ค€ ์ฒดํฌ๋ฆฌ์ŠคํŠธ
  • ① DNBR ≥ 1.30 (๋น„๋น„๋“ฑ ํŒŒ๊ดด ์ดํƒˆ๋น„)
    ์—ฐ๋ฃŒ๋ด‰ ๋น„ํ•ต๋น„๋“ฑ ํŒŒ๊ดด ๋ฐฉ์ง€. NRC ์„ค๊ณ„ ๊ธฐ์ค€ ์ดํƒˆ๋น„ ์ตœ์†Ÿ๊ฐ’
  • ② T_fuel_max ≤ 1482°C (UO₂ ์šฉ์œต์ )
    ํ•ต์—ฐ๋ฃŒ ์ตœ๋Œ€ ์˜จ๋„๊ฐ€ UO₂ ์šฉ์œต ์˜จ๋„ ์ดํ•˜ ์œ ์ง€
  • ③ T_clad_max ≤ 1200°C (ํ”ผ๋ณต๊ด€ ์„ค๊ณ„ ํ•œ๊ณ„)
    ์ง€๋ฅด์ฝ”๋Š„ ํ”ผ๋ณต๊ด€ ECCS ๋ƒ‰๊ฐ์ˆ˜ ๋ถ„์‚ฌ ๊ธฐ์ค€
  • ④ T_outlet ≤ 325°C (๋ƒ‰๊ฐ์žฌ ํฌํ™” ๋งˆ์ง„)
    1์ฐจ๊ณ„ ๋ƒ‰๊ฐ์žฌ ํฌํ™” ์˜จ๋„(@ 12.76 MPa: 331°C) ๋Œ€๋น„ ๋งˆ์ง„ ํ™•๋ณด
  • ⑤ k-eff ≤ 0.95 (๋ƒ‰๊ฐ์ˆ˜ ๋ฐฐ์ถœ ์ƒํƒœ)
    LOCA(๋ƒ‰๊ฐ์ˆ˜ ์ƒ์‹ค ์‚ฌ๊ณ ) ์‹œ ์ž„๊ณ„ ๋ฏธ๋‹ฌ ๋ณด์žฅ
  • ⑥ ๋ฐ˜์‘๋„ ํˆฌ์ž…์œจ ≤ 5 pcm/s (RIA)
    ๋ฐ˜์‘๋„ ์‚ฝ์ž… ์‚ฌ๊ณ  ์‹œ ์ด ๋ฐ˜์‘๋„ ์‚ฝ์ž… ์†๋„ ์ œํ•œ
  • ⑦ ์••๋ ฅ ๋งˆ์ง„ ≥ 10% (1์ฐจ๊ณ„ ์„ค๊ณ„์••๋ ฅ)
    ์šด์ „ ์ค‘ 1์ฐจ๊ณ„ ์••๋ ฅ์ด ์„ค๊ณ„ ์••๋ ฅ ๋Œ€๋น„ 10% ์ด์ƒ ๋งˆ์ง„ ์œ ์ง€
  • ⑧ ๋น„์ƒ ๋ƒ‰๊ฐ ์šฉ๋Ÿ‰ ≥ ์ž”์—ฌ ์—ด์ถœ๋ ฅ
    ์ˆ˜๋™ ๋ƒ‰๊ฐ ์‹œ์Šคํ…œ(NuScale DHRS)์˜ ์ž”์—ฌ ๋ฐœ์—ด ์ œ๊ฑฐ ๋Šฅ๋ ฅ
๐Ÿ’ป
SafetyPlugin ๊ตฌํ˜„ ์ฝ”๋“œ
python — safety_plugin.py
class SafetyPlugin:
    """
    NRC 10 CFR 50 ์•ˆ์ „ ๊ธฐ์ค€ ์ž๋™ ๊ฒ€์ฆ ํ”Œ๋Ÿฌ๊ทธ์ธ
    ─ DNBR, ์—ฐ๋ฃŒ ์˜จ๋„, ๋ƒ‰๊ฐ์žฌ ์˜จ๋„, ์••๋ ฅ ๋งˆ์ง„
    ─ LOCA / RIA ์‚ฌ๊ณ  ์‹œ๋‚˜๋ฆฌ์˜ค ์ฒดํฌ
    """

    # NRC ์„ค๊ณ„ ํ•œ๊ณ„ (NuScale SMR ๊ธฐ์ค€)
    DNBR_LIMIT    = 1.30    # ๋น„๋น„๋“ฑ ํŒŒ๊ดด ์ดํƒˆ๋น„
    T_FUEL_MAX    = 1482.0  # UO2 ์šฉ์œต ์˜จ๋„ (°C)
    T_CLAD_MAX    = 1200.0  # ECCS ํ”ผ๋ณต๊ด€ ํ•œ๊ณ„ (°C)
    T_COOLANT_MAX = 325.0   # ๋ƒ‰๊ฐ์žฌ ์ตœ๋Œ€ ์˜จ๋„ (°C)
    P_DESIGN      = 16.0    # 1์ฐจ๊ณ„ ์„ค๊ณ„ ์••๋ ฅ (MPa)
    P_MARGIN_MIN  = 0.10    # ์ตœ์†Œ ์••๋ ฅ ๋งˆ์ง„ (10%)

    def __init__(self):
        self.reactor = None
        self.results: dict = {}
        self.passed: bool = False

    def execute(self):
        """์ „์ฒด ์•ˆ์ „ ๊ธฐ์ค€ ์ผ๊ด„ ๊ฒ€์ฆ"""
        s = self.reactor.state
        self.results = {
            'dnbr_ok'    : s.dnbr_min >= self.DNBR_LIMIT,
            't_cool_ok'  : s.t_outlet <= self.T_COOLANT_MAX,
            'press_ok'   : (self.P_DESIGN - s.pressure) / self.P_DESIGN
                            >= self.P_MARGIN_MIN,
            'keff_ok'    : s.keff <= 1.10,  # ์šด์ „ ๋ฒ”์œ„
        }
        self.passed = all(self.results.values())

    def loca_analysis(self) -> dict:
        """๋ƒ‰๊ฐ์ˆ˜ ์ƒ์‹ค ์‚ฌ๊ณ  (LOCA) ๊ฐ„์ด ๋ถ„์„"""
        import numpy as np
        s = self.reactor.state

        # ์ž”์—ฌ ๋ฐœ์—ด (ANS-5.1 ๋ถ•๊ดด์—ด ๋ชจ๋ธ)
        t_arr = np.logspace(0, 7, 100)  # 1s ~ 10^7 s
        decay = s.power_mwth * 0.066 * (t_arr ** -0.2)  # MW

        # ํ”ผ๋™ ๋ƒ‰๊ฐ ๊ณ„ํ†ต (NuScale DHRS) ์šฉ๋Ÿ‰
        dhrs_capacity = 2.0  # MW (NuScale ์„ค๊ณ„๊ฐ’)

        return {
            'time_s'       : t_arr.tolist(),
            'decay_heat_mw': decay.tolist(),
            'dhrs_mw'      : dhrs_capacity,
            'safety_margin': (decay < dhrs_capacity).tolist(),
        }

    def report(self) -> str:
        lines = ["=== SMR Safety Analysis Report ==="]
        for k, v in self.results.items():
            status = "✅ PASS" if v else "❌ FAIL"
            lines.append(f"  {k:15s}: {status}")
        lines.append(f"\nOverall: {'✅ SAFE' if self.passed else '❌ UNSAFE'}")
        return "\n".join(lines)
์ž๋™ํ™” ์›Œํฌํ”Œ๋กœ (YAML → ์‹œ๋ฎฌ → ์ถœ๋ ฅ)
TerraPower ARMI ์Šคํƒ€์ผ End-to-End ํŒŒ์ดํ”„๋ผ์ธ
๐Ÿ“„
YAML ์ž…๋ ฅ
์„ค๊ณ„ ํŒŒ๋ผ๋ฏธํ„ฐ
๐Ÿ—
๋ชจ๋ธ ์ƒ์„ฑ
Reactor Hub
์ค‘์„ฑ์ž ํ•ด์„
k-eff ๊ณ„์‚ฐ
๐ŸŒก
์—ด์œ ์ฒด ํ•ด์„
DNBR · T
๐Ÿ›ก
์•ˆ์ „ ๊ฒ€์ฆ
NRC ๊ธฐ์ค€
๐Ÿ“Š
๊ฒฐ๊ณผ ์ถœ๋ ฅ
CSV · PNG
[──────]์Šคํ…์„ ํด๋ฆญํ•˜๊ฑฐ๋‚˜ ์ „์ฒด ์›Œํฌํ”Œ๋กœ ์‹คํ–‰ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด์„ธ์š”.
๐Ÿ“
์‹œํ€€์Šค ๋‹ค์ด์–ด๊ทธ๋žจ — Hub-and-Spoke ์‹คํ–‰ ํ๋ฆ„
main.py Reactor Hub NeutronicsPlugin ThermalPlugin SafetyPlugin Reactor(config.yaml) register_plugin('neut') register_plugin('th') reactor.run() neutronics.execute() keff=1.024 thermal.execute() T_out=285°C, DNBR=2.41 safety.execute() passed=True ✅ return ReactorState
๐Ÿ”ฌ
ORNL Modelica ๋ฒค์น˜๋งˆํ‚น — ๊ฒ€์ฆ ๊ธฐ์ค€๊ฐ’
Oak Ridge National Laboratory SMR ๊ฒ€์ฆ ๋ฐ์ดํ„ฐ์™€ ๋น„๊ต
๋ฌผ๋ฆฌ๋Ÿ‰ ORNL ๊ธฐ์ค€๊ฐ’ ํ—ฌํผ ํ”„๋กœ๊ทธ๋žจ ์˜ค์ฐจ ์ƒํƒœ
k-eff (์ดˆ๊ธฐ) 1.024 ~ 1.028 1.024 < 0.4% ✅ ์ ํ•ฉ
T_outlet (°C) 283 ~ 287 285 < 0.7% ✅ ์ ํ•ฉ
DNBR_min ≥ 2.2 2.41 +9.5% ✅ ๋ณด์ˆ˜์ 
์—ด์ถœ๋ ฅ (MWth) 77.0 ± 2.0 77.0 0.0% ✅ ์ผ์น˜
์—ฐ์†Œ๋„ ๋ชจ๋ธ 4์ธ์ž ๊ณต์‹ ์ˆ˜์ค€ 4์ธ์ž ๊ทผ์‚ฌ ๊ฐœ๋…์  ์ˆ˜์ค€ ⚠ ๊ฐœ์„  ํ•„์š”

์ด ๋ธ”๋กœ๊ทธ์˜ ์ธ๊ธฐ ๊ฒŒ์‹œ๋ฌผ

ํด๋กœ๋“œ ์ฝ”๋“œ React TypeScript ์Šคํƒ€์ผ ์ผ๊ด€์„ฑ ์œ ์ง€ ํŒ

๊ธฐํ˜ธ๋กœ ํ”„๋กœ์ ํŠธ ์ปจํ…์ŠคํŠธ ์‹ค์‹œ๊ฐ„ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฒ•

ํด๋กœ๋“œ ์ฝ”๋“œ n8n ์›Œํฌํ”Œ๋กœ์šฐ ์ž๋™ํ™” ์—ฐ๋™ ๊ฐ€์ด๋“œ