Obsidian/Presentations/ERLM/two_loop_sg_uncertainty.jl
Dane Sabo ed59b30dd0 Auto sync: 2025-12-07 20:55:06 (25 files changed)
D  Presentations/ERLM/bouncing_ball_hybrid.png

A  Presentations/ERLM/images/4_research_approach/phase_portrait_sg1.png

A  Presentations/ERLM/images/4_research_approach/two_loop.png

A  Presentations/ERLM/images/7_broader_impacts/billion.jpg

M  Presentations/ERLM/main.aux

M  Presentations/ERLM/main.fdb_latexmk

M  Presentations/ERLM/main.fls

M  Presentations/ERLM/main.log
2025-12-07 20:55:06 -05:00

180 lines
6.6 KiB
Julia

using DifferentialEquations
using Plots
"""
Two-Loop Reactor: SG1 Heat Removal Uncertainty
Simple, realistic scenario:
- SG2 efficiency is known: Q2 = 50.0
- SG1 efficiency is uncertain: Q1 ∈ [45, 55] (±10% due to fouling/conditions)
- Everything else is exact!
This shows how uncertainty in ONE component propagates through the system.
"""
println("=== Two-Loop Reactor: SG1 Heat Removal Uncertainty ===\n")
# Constants from Python script
const C_0 = 33.33 # Base Heat Capacity [%-sec/°F]
const τ_0 = 0.75 * C_0 # Base Time Constant [sec]
const μ = 0.6 # Fixed reactor water mass fraction
const P_r = 100.0 # Reactor power
const Q2 = 50.0 # SG2 heat removal (KNOWN)
println("=== Parameters ===")
println("C_0 = $C_0 %-sec/°F")
println("τ_0 = $τ_0 sec")
println("μ = (fixed)")
println("P_r = $P_r")
println("Q2 = $Q2 (SG2 - KNOWN)")
# Initial conditions (non-equilibrium for interesting dynamics)
const T_hot_0 = 455.0 # Hot leg slightly elevated
const T_cold1_0 = 450.0 # Cold legs at reference
const T_cold2_0 = 450.0
println("\n=== Initial Conditions ===")
println("T_hot_0 = $T_hot_0 °F")
println("T_cold1_0 = $T_cold1_0 °F")
println("T_cold2_0 = $T_cold2_0 °F")
# Two-loop ODEs with Q1 as parameter
function two_loop!(du, u, p, t)
T_hot, T_cold1, T_cold2 = u
Q1 = p[1] # SG1 heat removal (UNCERTAIN)
# System parameters
C_r = μ * C_0
C_sg = (1 - μ) * C_0 / 2
W = C_0 / (2 * τ_0)
# Energy balances
du[1] = (P_r - W * (T_hot - T_cold1) - W * (T_hot - T_cold2)) / C_r
du[2] = (W * (T_hot - T_cold1) - Q1) / C_sg # Q1 varies!
du[3] = (W * (T_hot - T_cold2) - Q2) / C_sg # Q2 fixed
return du
end
# Q1 uncertainty range
Q1_values = range(45.0, 55.0, length=40) # Fine sweep over ±10%
println("\n=== Q1 Uncertainty (SG1 Heat Removal) ===")
println("Q1 range: [45.0, 55.0] (±10% from nominal 50)")
println("Number of cases: $(length(Q1_values))")
println("\nPhysical meaning:")
println(" Q1 = 45: SG1 is 10% less efficient (fouling, poor heat transfer)")
println(" Q1 = 55: SG1 is 10% more efficient (better conditions)")
# Solve for all Q1 values
u0 = [T_hot_0, T_cold1_0, T_cold2_0]
tspan = (0.0, 30.0)
t_save = range(0, 30, length=300)
println("\nSolving...")
all_sols = []
for Q1 in Q1_values
p = [Q1]
prob = ODEProblem(two_loop!, u0, tspan, p)
sol = solve(prob, Tsit5(), saveat=t_save)
push!(all_sols, sol)
end
println("Done!")
# Extract data
times = all_sols[1].t
all_T_hot = [[sol.u[i][1] for i in 1:length(sol)] for sol in all_sols]
all_T_cold1 = [[sol.u[i][2] for i in 1:length(sol)] for sol in all_sols]
all_T_cold2 = [[sol.u[i][3] for i in 1:length(sol)] for sol in all_sols]
# Compute T_ave
all_T_ave = []
for (i, Q1) in enumerate(Q1_values)
T_ave = μ * all_T_hot[i] .+ (1 - μ) * (all_T_cold1[i] .+ all_T_cold2[i]) ./ 2
push!(all_T_ave, T_ave)
end
# Compute reach tube envelopes
T_hot_min = [minimum([all_T_hot[i][j] for i in 1:length(Q1_values)]) for j in 1:length(times)]
T_hot_max = [maximum([all_T_hot[i][j] for i in 1:length(Q1_values)]) for j in 1:length(times)]
T_cold1_min = [minimum([all_T_cold1[i][j] for i in 1:length(Q1_values)]) for j in 1:length(times)]
T_cold1_max = [maximum([all_T_cold1[i][j] for i in 1:length(Q1_values)]) for j in 1:length(times)]
T_ave_min = [minimum([all_T_ave[i][j] for i in 1:length(Q1_values)]) for j in 1:length(times)]
T_ave_max = [maximum([all_T_ave[i][j] for i in 1:length(Q1_values)]) for j in 1:length(times)]
println("\n=== Creating Plots ===")
# Plot 1: T_hot reach tube
p1 = plot(times, T_hot_min, fillrange=T_hot_max,
xlabel="Time (s)", ylabel="T_hot (°F)",
title="Hot Leg Temperature",
fillalpha=0.4, color=:red, lw=2.5,
label="Reach tube", legend=:topright)
# Plot 2: T_cold1 reach tube (affected by Q1 uncertainty)
p2 = plot(times, T_cold1_min, fillrange=T_cold1_max,
xlabel="Time (s)", ylabel="T_cold1 (°F)",
title="Cold Leg 1 (SG1 - Uncertain)",
fillalpha=0.4, color=:blue, lw=2.5,
label="Reach tube", legend=:topright)
# Plot 3: T_ave reach tube
p3 = plot(times, T_ave_min, fillrange=T_ave_max,
xlabel="Time (s)", ylabel="T_avg (°F)",
title="Average Primary Temperature",
fillalpha=0.4, color=:orange, lw=2.5,
label="Reach tube", legend=:topright)
# Plot 4: Phase portrait T_hot vs T_ave
p4 = plot(xlabel="T_avg (°F)", ylabel="T_hot (°F)",
title="Phase Portrait: T_hot vs T_avg",
legend=:topright)
# Plot envelope
plot!(p4, [T_ave_min; reverse(T_ave_max)], [T_hot_min; reverse(T_hot_max)],
seriestype=:shape, fillalpha=0.3, color=:purple,
label="Reachable region", lw=2.5)
# Mark extremes
plot!(p4, T_ave_min, T_hot_min, color=:blue, lw=2.5, label="Q1 = 45", linestyle=:dash)
plot!(p4, T_ave_max, T_hot_max, color=:red, lw=2.5, label="Q1 = 55", linestyle=:dash)
plot_combined = plot(p1, p2, p3, p4, layout=(2, 2), size=(1400, 1000),
plot_title="SG1 Heat Removal Uncertainty: Q1 ∈ [45, 55]")
savefig(plot_combined, "sg1_uncertainty.png")
println("Saved: sg1_uncertainty.png")
# Detailed phase portrait
p_phase = plot(xlabel="T_avg (°F)", ylabel="T_hot (°F)",
title="Phase Portrait: T_hot vs T_avg\n(SG1 efficiency uncertainty: Q1 ∈ [45, 55])",
size=(1000, 900), legend=:topright)
# Plot all trajectories
for (i, Q1) in enumerate(Q1_values)
color_val = (Q1 - minimum(Q1_values)) / (maximum(Q1_values) - minimum(Q1_values))
plot!(p_phase, all_T_ave[i], all_T_hot[i],
color=cgrad(:RdBu)[color_val], lw=1.5, alpha=0.3, label="")
end
# Add envelope
plot!(p_phase, [T_ave_min; reverse(T_ave_max)], [T_hot_min; reverse(T_hot_max)],
seriestype=:shape, fillalpha=0.25, color=:purple,
label="Reachable region", lw=3)
# Mark extremes
plot!(p_phase, all_T_ave[1], all_T_hot[1],
color=:blue, lw=3.5, label="Q1 = 45 (low efficiency)", linestyle=:dash)
plot!(p_phase, all_T_ave[end], all_T_hot[end],
color=:red, lw=3.5, label="Q1 = 55 (high efficiency)", linestyle=:dash)
savefig(p_phase, "phase_portrait_sg1.png")
println("Saved: phase_portrait_sg1.png")
println("\n✓ Complete!")
println("\n=== Results ===")
println("T_hot range: [$(round(minimum(T_hot_min), digits=1)), $(round(maximum(T_hot_max), digits=1))] °F")
println("T_avg range: [$(round(minimum(T_ave_min), digits=1)), $(round(maximum(T_ave_max), digits=1))] °F")
println("T_cold1 range: [$(round(minimum(T_cold1_min), digits=1)), $(round(maximum(T_cold1_max), digits=1))] °F")
println("\nΔT_hot = $(round(maximum(T_hot_max) - minimum(T_hot_min), digits=1)) °F")
println("ΔT_avg = $(round(maximum(T_ave_max) - minimum(T_ave_min), digits=1)) °F")