Skip to content

Commit c7119f9

Browse files
most recent model
1 parent 0b7ba3e commit c7119f9

File tree

2 files changed

+138
-97
lines changed

2 files changed

+138
-97
lines changed

src/components/nodes/GLCNode.jsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export function GLCNode({ data }) {
6464
fontWeight: 'bold',
6565
textAlign: 'right'
6666
}}>
67-
T_out_liquid
67+
c_T_oulet
6868
</div>
6969

7070
<div style={{
@@ -75,11 +75,11 @@ export function GLCNode({ data }) {
7575
fontWeight: 'bold',
7676
textAlign: 'right'
7777
}}>
78-
T_out_gas
78+
P_T2_out_gas
7979
</div>
8080

81-
<Handle type="source" position="right" id="T_out_liquid" style={{ background: '#555', top: '33%' }} />
82-
<Handle type="source" position="right" id="T_out_gas" style={{ background: '#555', top: '66%' }} />
81+
<Handle type="source" position="right" id="c_T_outlet" style={{ background: '#555', top: '33%' }} />
82+
<Handle type="source" position="right" id="P_T2_out_gas" style={{ background: '#555', top: '66%' }} />
8383
<Handle type="source" position="right" id="efficiency" style={{ background: '#555', top: '10%' }} />
8484
</div>
8585
);

src/python/custom_pathsim_blocks.py

Lines changed: 134 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ def update(self, t):
409409
import numpy as np
410410
from scipy.integrate import solve_bvp
411411
from scipy import constants as const
412+
from scipy.optimize import root_scalar
412413

413414

414415
def solve(params):
@@ -417,10 +418,9 @@ def solve_tritium_extraction(dimensionless_params, y_T2_in, elements):
417418
Solves the BVP for tritium extraction in a bubble column.
418419
419420
Args:
420-
dimensionless_params (dict): A dictionary containing the dimensionless parameters:
421+
params (dict): A dictionary containing the dimensionless parameters:
421422
Bo_l, phi_l, Bo_g, phi_g, psi, nu.
422423
y_T2_in (float): Inlet tritium molar fraction in the gas phase, y_T2(0-).
423-
elements (int): Number of mesh elements for the solver.
424424
425425
Returns:
426426
sol: The solution object from scipy.integrate.solve_bvp.
@@ -440,29 +440,32 @@ def ode_system(xi, S):
440440
S[1] = dx_T/d(xi)
441441
S[2] = y_T2 (dimensionless gas concentration)
442442
S[3] = dy_T2/d(xi)
443+
444+
x_T = c_T / c_T(L+)
445+
xi = z / L (dimensionless position)
443446
"""
444447
x_T, dx_T_dxi, y_T2, dy_T2_dxi = S
445448

446-
# Dimensionless driving force theta. Eq. 8.8
447-
theta = x_T - np.sqrt(np.maximum(0, (1 - psi * xi) * y_T2 / nu))
449+
# Dimensionless driving force theta. Eq. 12
450+
theta = x_T - np.sqrt(((1 - (psi * xi)) / nu) * y_T2) # MATCHES PAPER
448451

449452
# Equation for d(S[0])/d(xi) = d(x_T)/d(xi)
450453
dS0_dxi = dx_T_dxi
451454

452455
# Equation for d(S[1])/d(xi) = d^2(x_T)/d(xi)^2
453-
dS1_dxi = Bo_l * (phi_l * theta - dx_T_dxi) # Eq. 9.1.4
456+
dS1_dxi = Bo_l * (phi_l * theta - dx_T_dxi)
454457

455458
# Equation for d(S[2])/d(xi) = d(y_T2)/d(xi)
456459
dS2_dxi = dy_T2_dxi
457460

458461
# Equation for d(S[3])/d(xi) = d^2(y_T2)/d(xi)^2 from eq (11)
459462
# Avoid division by zero if (1 - psi * xi) is close to zero at xi=1
460-
denominator = 1 - psi * xi
461-
denominator = np.where(np.isclose(denominator, 0), 1e-9, denominator)
462463

463-
term1 = (1 + 2 * psi / Bo_g) * dy_T2_dxi
464-
term2 = phi_g * theta
465-
dS3_dxi = (Bo_g / denominator) * (term1 - term2)
464+
term1 = (1 + 2 * psi / Bo_g) * dy_T2_dxi # Part of Eq. 9.3.3 (fourth line)
465+
term2 = phi_g * theta # Part of Eq. 9.3.3 (fourth line)
466+
dS3_dxi = (Bo_g / (1 - psi * xi)) * (
467+
term1 - term2
468+
) # Eq. 9.3.3 (fourth line)
466469

467470
return np.vstack((dS0_dxi, dS1_dxi, dS2_dxi, dS3_dxi))
468471

@@ -492,82 +495,150 @@ def boundary_conditions(Sa, Sb):
492495
# Set up the mesh and an initial guess for the solver.
493496
xi = np.linspace(0, 1, elements + 1)
494497

495-
# An initial guess that is physically plausible can significantly help convergence.
496-
# We expect liquid concentration (x_T) to decrease from inlet (xi=1) to outlet (xi=0).
497-
# We expect gas concentration (y_T2) to increase from inlet (xi=0) to outlet (xi=1).
498-
# y_guess = np.zeros((4, xi.size))
499-
# y_guess[0, :] = np.linspace(
500-
# 0.5, 1.0, xi.size
501-
# ) # Guess for x_T (linear decrease)
502-
# y_guess[1, :] = -0.5 # Guess for dx_T/dxi
503-
# y_guess[2, :] = np.linspace(
504-
# y_T2_in, y_T2_in + 1e-4, xi.size
505-
# ) # Guess for y_T2 (linear increase)
506-
# y_guess[3, :] = 1e-4 # Guess for dy_T2/dxi
507-
508-
# a zero initial guess works better for low concentrations
509498
y_guess = np.zeros((4, xi.size))
510499

511500
# Run the BVP solver
512501
sol = solve_bvp(
513-
ode_system, boundary_conditions, xi, y_guess, tol=1e-5, max_nodes=10000
502+
ode_system, boundary_conditions, xi, y_guess, tol=1e-6, max_nodes=10000
514503
)
515504

516505
return sol
517506

518507
# Unpack parameters
519508
c_T_inlet = params["c_T_inlet"]
520509
y_T2_in = params["y_T2_in"]
521-
P_outlet = params["P_outlet"]
522-
ρ_l = params["ρ_l"]
523-
K_s = params["K_s"]
510+
P_0 = params["P_0"]
524511

525512
L = params["L"]
526513
D = params["D"]
527-
ε_g = params["ε_g"]
528-
529-
Q_l = params["Q_l"]
530-
Q_g = params["Q_g"]
531-
532-
a = params["a"]
533514

534-
E_g = params["E_g"]
535-
E_l = params["E_l"]
536-
537-
h_l = params["h_l"]
515+
flow_l = params["flow_l"]
516+
u_g0 = params["u_g0"]
538517

539518
g = params["g"]
540519
T = params["T"]
541520

542521
elements = params["elements"] # Number of mesh elements for solver
543522

544-
# Calculate inlet pressure hydrostatically
545-
assert P_outlet > 0, "P_outlet must be greater than zero."
546-
P_0 = P_outlet + ρ_l * g * L
523+
# --- Constants ---
524+
g = const.g # m/s^2, Gravitational acceleration
525+
R = const.R # J/(mol·K), Universal gas constant
526+
N_A = const.N_A # 1/mol, Avogadro's number
527+
M_LiPb = 2.875e-25 # Kg/molecule, Lipb molecular mass
528+
529+
# Calculate empirical correlations
530+
ρ_l = 10.45e3 * (1 - 1.61e-4 * T) # kg/m^3, Liquid (LiPb) density
531+
σ_l = 0.52 - 0.11e-3 * T # N/m, Surface tension, liquid (LiPb) - gas (He) interface
532+
μ_l = 1.87e-4 * np.exp(11640 / (R * T)) # Pa.s, Dynamic viscosity of liquid LiPb
533+
ν_l = μ_l / ρ_l # m^2/s, Kinematic viscosity of liquid LiPb
534+
D_T = 2.5e-7 * np.exp(
535+
-27000 / (R * T)
536+
) # m^2/s, Tritium diffusion coefficient in liquid LiPb
537+
538+
K_s = 2.32e-8 * np.exp(
539+
-1350 / (R * T)
540+
) # atfrac*Pa^0.5, Sievert's constant for tritium in liquid LiPb
541+
K_s = K_s * (ρ_l / (M_LiPb * N_A)) # mol/(m^3·Pa^0.5)
547542

548-
# Calculate the superficial flow velocities
549543
A = np.pi * (D / 2) ** 2 # m^2, Cross-sectional area of the column
550-
u_g0 = Q_g / A # m/s, superficial gas inlet velocity
544+
545+
# Calculate the volumetric flow rates
546+
Q_l = flow_l / ρ_l # m^3/s, Volumetric flow rate of liquid phase
547+
Q_g = u_g0 * A # m^3/s, Volumetric flow rate of gas phase
548+
549+
# Calculate the superficial flow velocities
550+
# u_g0 = Q_g / A # m/s, superficial gas inlet velocity
551551
u_l = Q_l / A # m/s, superficial liquid inlet velocity
552552

553-
# Calculate dimensionless values
553+
# Calculate Bond, Galilei, Schmidt and Froude numbers
554+
Bn = (g * D**2 * ρ_l) / σ_l # Bond number
555+
Ga = (g * D**3) / ν_l**2 # Galilei number
556+
Sc = ν_l / D_T # Schmidt number
557+
Fr = u_g0 / (g * D) ** 0.5 # Froude number
558+
559+
# Calculate dispersion coefficients
560+
E_l = (D * u_g0) / (
561+
(13 * Fr) / (1 + 6.5 * (Fr**0.8))
562+
) # m^2/s, Effective axial dispersion coefficient, liquid phase
563+
E_g = (
564+
0.2 * D**2
565+
) * u_g0 # m^2/s, Effective axial dispersion coefficient, gas phase
566+
567+
# Calculate gas hold-up (phase fraction) & mass transfer coefficient
568+
C = 0.2 * (Bn ** (1 / 8)) * (Ga ** (1 / 12)) * Fr # C = ε_g / (1 - ε_g)^4
569+
570+
def solveEqn(ε_g, C):
571+
# Define the equation to solve
572+
eqn = ε_g / (1 - ε_g) ** 4 - C
573+
return eqn
574+
575+
ε_g_initial_guess = 0.1
576+
try:
577+
# bracket=[0.0001, 0.9999] tells it to *only* look in this range
578+
sol = root_scalar(solveEqn, args=(C,), bracket=[0.00001, 0.99999])
579+
580+
# print(f"--- Using root_scalar (robust method) ---")
581+
# print(f"C value was: {C}")
582+
# if sol.converged:
583+
# print(f"Solved gas hold-up (εg): {sol.root:.6f}")
584+
# # Verify it
585+
# verification = sol.root / (1 - sol.root)**4
586+
# print(f"Verification (should equal C): {verification:.6f}")
587+
# else:
588+
# print("Solver did not converge.")
589+
590+
except ValueError as e:
591+
print(
592+
f"Solver failed. This can happen if C is so large that no solution exists between 0 and 1."
593+
)
594+
print(f"Error: {e}")
595+
596+
ε_g = sol.root # Gas phase fraction
554597
ε_l = 1 - ε_g # Liquid phase fraction
555-
ψ = (ρ_l * g * ε_l * L) / P_0 # Hydrostatic pressure ratio (Eq. 8.3)
556-
ν = (c_T_inlet / K_s) ** 2 / P_0 # Tritium partial pressure ratio (Eq. 8.5)
557-
Bo_l = u_l * L / (ε_l * E_l) # Bodenstein number, liquid phase (Eq. 8.9)
558-
phi_l = a * h_l * L / u_l # Transfer units parameter, liquid phase (Eq. 8.11)
559-
Bo_g = u_g0 * L / (ε_g * E_g) # Bodenstein number, gas phase (Eq. 8.10)
560-
phi_g = (
561-
0.5 * (const.R * T * c_T_inlet / P_0) * (a * h_l * L / u_g0)
562-
) # Transfer units parameter, gas phase (Eq. 8.12)
598+
599+
# Calculate outlet pressure hydrostatically & check non-negative
600+
P_outlet = P_0 - (ρ_l * (1 - ε_g) * g * L)
601+
602+
if P_outlet <= 0:
603+
raise ValueError(
604+
f"Calculated gas outlet pressure P_outlet must be positive, but got {P_outlet:.2e} Pa. Check P_0, rho_l, g, and L are realistic."
605+
)
606+
607+
# Calculate interfacial area
608+
d_b = (
609+
26 * (Bn**-0.5) * (Ga**-0.12) * (Fr**-0.12)
610+
) * D # m, Mean bubble diameter AGREES WITH PAPER
611+
a = 6 * ε_g / d_b # m^-1, Specific interfacial area, assuming spherical bubbles
612+
613+
# Calculate volumetric mass transfer coefficient, liquid-gas
614+
h_l_a = (
615+
D_T * (0.6 * Sc**0.5 * Bn**0.62 * Ga**0.31 * ε_g**1.1) / (D**2)
616+
) # Volumetric mass transfer coefficient, liquid-gas
617+
618+
h_l = h_l_a / a # Mass transfer coefficient
619+
620+
# Calculate dimensionless values
621+
622+
# Hydrostatic pressure ratio (Eq. 8.3) # MATCHES PAPER
623+
psi = (ρ_l * g * (1 - ε_g) * L) / P_0
624+
# Tritium partial pressure ratio (Eq. 8.5) # MATCHES PAPER
625+
nu = ((c_T_inlet / K_s) ** 2) / P_0
626+
# Bodenstein number, liquid phase (Eq. 8.9) # MATCHES PAPER
627+
Bo_l = u_l * L / (ε_l * E_l)
628+
# Transfer units parameter, liquid phase (Eq. 8.11) # MATCHES PAPER
629+
phi_l = a * h_l * L / u_l
630+
# Bodenstein number, gas phase (Eq. 8.10) # MATCHES PAPER ASSUMING u_g0
631+
Bo_g = u_g0 * L / (ε_g * E_g)
632+
# Transfer units parameter, gas phase (Eq. 8.12) # MATCHES PAPER
633+
phi_g = 0.5 * (R * T * c_T_inlet / P_0) * (a * h_l * L / u_g0)
563634

564635
dimensionless_params = {
565636
"Bo_l": Bo_l,
566637
"phi_l": phi_l,
567638
"Bo_g": Bo_g,
568639
"phi_g": phi_g,
569-
"psi": ψ,
570-
"nu": ν,
640+
"psi": psi,
641+
"nu": nu,
571642
}
572643

573644
# Solve the model
@@ -638,20 +709,10 @@ class GLC(pathsim.blocks.Function):
638709
More details about the model can be found in: https://doi.org/10.13182/FST95-A30485
639710
640711
Args:
641-
P_outlet: Outlet operating pressure [Pa]
712+
P_0: Inlet operating pressure [Pa]
642713
L: Column height [m]
643-
u_l: Superficial liquid velocity [m/s]
644714
u_g0: Superficial gas inlet velocity [m/s]
645-
ε_g: Gas phase fraction [-]
646-
ε_l: Liquid phase fraction [-]
647-
E_g: Gas phase axial dispersion coefficient [m^2/s]
648-
E_l: Liquid phase axial dispersion coefficient [m^2/s]
649-
a: Specific interfacial area [m^2/m^3]
650-
h_l: Liquid phase volumetric mass transfer coefficient [m/s]
651-
ρ_l: Liquid density [kg/m^3]
652-
K_s: Tritium solubility constant [mol/(m^3·Pa)^0.5]
653715
Q_l: Liquid volumetric flow rate [m^3/s]
654-
Q_g: Gas volumetric flow rate [m^3/s]
655716
D: Column diameter [m]
656717
T: Temperature [K]
657718
g: Gravitational acceleration [m/s^2], default is 9.81
@@ -662,47 +723,27 @@ class GLC(pathsim.blocks.Function):
662723
"y_T2_in": 1,
663724
}
664725
_port_map_out = {
665-
"T_out_liquid": 0,
666-
"T_out_gas": 1,
726+
"c_T_outlet": 0,
727+
"P_T2_out_gas": 1,
667728
"efficiency": 2,
668729
}
669730

670731
def __init__(
671732
self,
672-
P_outlet,
733+
P_0,
673734
L,
674-
u_l,
675735
u_g0,
676-
ε_g,
677-
ε_l,
678-
E_g,
679-
E_l,
680-
a,
681-
h_l,
682-
ρ_l,
683-
K_s,
684-
Q_l,
685-
Q_g,
736+
flow_l,
686737
D,
687738
T,
688-
g=9.81,
739+
g=const.g,
689740
initial_nb_of_elements=20,
690741
):
691742
self.params = {
692-
"P_outlet": P_outlet,
743+
"P_0": P_0,
693744
"L": L,
694-
"u_l": u_l,
695745
"u_g0": u_g0,
696-
"ε_g": ε_g,
697-
"ε_l": ε_l,
698-
"E_g": E_g,
699-
"E_l": E_l,
700-
"a": a,
701-
"h_l": h_l,
702-
"ρ_l": ρ_l,
703-
"K_s": K_s,
704-
"Q_l": Q_l,
705-
"Q_g": Q_g,
746+
"flow_l": flow_l,
706747
"g": g,
707748
"D": D,
708749
"T": T,
@@ -724,4 +765,4 @@ def func(self, c_T_inlet, y_T2_inlet):
724765
n_T_out_gas = res["tritium_out_gas [mol/s]"]
725766
eff = res["extraction_efficiency [%]"]
726767

727-
return n_T_out_liquid, n_T_out_gas, eff
768+
return c_T_outlet, P_T2_outlet, eff

0 commit comments

Comments
 (0)